Skip to content

Configuration ​

lnkify is configured entirely through environment variables defined in a .env file at the repository root. Docker Compose reads this file and injects the variables into the appropriate containers.

The .env File ​

The .env file uses standard KEY=VALUE syntax. Lines starting with # are comments. Values should be quoted if they contain spaces or special characters.

Example:

env
# Required
JWT_SECRET=a1b2c3d4e5f6...
APIKEY_PEPPER=f6e5d4c3b2a1...
DATABASE_URL=postgresql://postgres:password@db:5432/lnkify

# Optional
IPREGISTRY_API_KEY=ir_abc123...
MCP_ENABLED=true
PORT=4000

Environment Variables by Service ​

Server ​

The server container is the most configurable. It reads all variables defined in the env module from the .env file.

VariableRequiredTypeDefaultDescription
JWT_SECRETYesstring—Secret key used to sign JWT authentication tokens.
DATABASE_URLYesstring—PostgreSQL connection string (e.g. postgresql://user:password@host:port/db).
PORTNostring—HTTP port the server listens on (default: 4000).
NODE_ENVNostring—Environment mode (development, production, test).
TRUST_PROXYNostring—Number of trusted proxy hops in front of the server. Default 1 = single Caddy hop. Only raise if you add more trusted proxies; never use "true" in production (trusts entire attacker-controlled XFF chain).
IPREGISTRY_API_KEYNostring—API key for ipregistry.co geolocation lookups. Optional but enables per-click IP data.
APIKEY_PEPPERYesstring—Pepper value (HMAC secret) used to hash API keys before storage. Required.
RATELIMIT_GRAPHQL_PER_MINNostring120Max GraphQL requests per minute per identity bucket.
RATELIMIT_MCP_PER_MINNostring120Max MCP requests per minute per identity bucket.
RATELIMIT_REDIRECT_PER_MINNostring600Max redirect follows per minute per IP.
RATELIMIT_BULK_PER_MINNostring10Max bulk operations per minute.
BULK_MAX_ITEMSNostring100Max items allowed in a single bulk create/update/delete call.
MCP_ENABLEDNostringtrueEnable the Model Context Protocol server.
MCP_PATHNostring/mcpMount path for the MCP server.
REQUIRE_AUTH_FOR_CREATENobooleanfalseRequire authentication to create shortlinks. When false, anonymous links are allowed.
AUTH_RATE_LIMIT_WINDOW_MINUTESNostring1Window (minutes) for the strict auth-mutation rate-limit bucket — login/signup/resetPassword.
AUTH_RATE_LIMIT_MAXNostring5Max auth mutations per short window per IP.
AUTH_RATE_LIMIT_HOUR_WINDOW_MINUTESNostring60Window (minutes) for the long auth-mutation rate-limit bucket.
AUTH_RATE_LIMIT_HOUR_MAXNostring20Max auth mutations per long window per IP.
LOG_LEVELNostring—pino log level (trace, debug, info, warn, error, fatal). Defaults to info (debug in development).
SENTRY_DSNNostring—Sentry DSN for error tracking. Error reporting is disabled when unset.
IP_ANONYMIZENobooleanfalseAnonymize stored client IPs on hit records for privacy.
IP_RETENTION_DAYSNonumber—Auto-purge hit records older than this many days. Unset disables retention purging.
MCP_SCOPESNolist—Comma-separated OAuth-style scopes granted to MCP credentials. Defaults to full access (["*"]).
REDIS_URLNostring—Redis connection URL. When set, rate-limit, MCP session, and idempotency state use Redis (required for multi-replica); in-memory otherwise.
MCP_SESSION_TTL_MSNonumber1800000Idle MCP session time-to-live (ms) before it is reaped (default: 30 min).
MCP_MAX_SESSIONSNonumber100Global cap on concurrent MCP sessions across all identities.
MCP_MAX_SESSIONS_PER_IDENTITYNonumber10Cap on concurrent MCP sessions per identity (user/API key).
SLUG_CACHE_TTL_MSNonumber60000In-memory slug→target redirect cache entry TTL (ms) (default: 1 min).
SLUG_CACHE_MAX_SIZENonumber50000Maximum number of entries in the in-memory slug→target redirect cache.
CORS_ORIGINNostring—Allowed CORS origin for the API. Unset means same-origin only (no cross-origin CORS headers emitted).
DIRECT_URLNostring—Direct (session-mode) Postgres URL used by prisma migrate / db-init. Needed when DATABASE_URL points at a transaction pooler, since migrations can't run over one.
APP_URLNo*stringhttps://app.lnkify.ioPublic dashboard origin (the app. subdomain). Used for the / → /dash redirect, Stripe return URLs, and upgrade links. Required in production.
PUBLIC_BASE_URLNo*stringhttps://lnkify.ioPublic apex origin where short links and bio pages resolve. Used to build absolute short URLs (e.g. QR codes). Required in production.
MCP_PUBLIC_URLNo*stringhttps://mcp.lnkify.ioPublic origin of the MCP server, advertised in llms.txt. Required in production.
DOCS_URLNo*stringhttps://docs.lnkify.ioPublic docs origin, advertised in llms.txt. Required in production.
CADDY_TLS_CHECK_SECRETNostring—Shared secret Caddy sends as X-Tls-Check-Secret to /internal/tls-check for on-demand (custom-domain) TLS. When set, the endpoint fails closed without a matching header.
APIKEY_MAX_TTL_DAYSNonumber365Maximum lifetime (days) for a user-created API key; createApiKey rejects longer expiries.
MCP_INIT_RATE_LIMIT_PER_MINNonumber20Strict per-IP limit for unauthenticated MCP requests (session init / failed auth), tighter than the general MCP bucket.

* These origin variables fall back to the hosted defaults in development but are required in production (NODE_ENV=production).

Note: The canonical variable list lives in server/src/utils/env.ts; docs/scripts/generate-env.js mirrors it into .vitepress/data/env.json. Keep this table in sync when the env module changes.

Billing (Stripe) ​

Billing is optional and off by default. A standard self-host leaves all of these unset — usage is counted but never blocked. Set them only if you want to enforce plan quotas and charge through Stripe. See Billing & Usage.

VariableTypeDefaultDescription
BILLING_ENFORCEbooleanfalseEnforce plan quotas and feature gates. When false, usage is counted but not blocked (shadow mode).
BILLING_GRACE_DAYSnumber3Days a PAST_DUE subscription keeps paid entitlements before falling back to FREE.
STRIPE_SECRET_KEYstring—Stripe secret API key for server-side billing. Billing is disabled when unset.
STRIPE_WEBHOOK_SECRETstring—Stripe webhook signing secret used to verify POST /webhooks/stripe.
STRIPE_AUTO_TAXbooleanfalseEnable Stripe automatic tax collection.
STRIPE_PRICE_STARTER_MONTHLY, STRIPE_PRICE_STARTER_ANNUALstring—Stripe Price IDs for the Starter subscription.
STRIPE_PRICE_PRO_MONTHLY, STRIPE_PRICE_PRO_ANNUALstring—Stripe Price IDs for the Pro subscription.
STRIPE_PRICE_REFILL_STARTER, STRIPE_PRICE_REFILL_PROstring—Stripe Price IDs for the one-off auto-refill link packs.
BILLING_SUCCESS_URL, BILLING_CANCEL_URLstring—Redirect URLs after a completed or cancelled Stripe Checkout/Portal session.

Database ​

VariableDefaultDescription
POSTGRES_USERpostgresPostgreSQL user.
POSTGRES_PASSWORD(from .env)PostgreSQL password.
POSTGRES_DBlnkifyPostgreSQL database name.

Caddy ​

Caddy is configured via Caddyfile at the repository root, not through environment variables. See Reverse Proxy for the Caddyfile walkthrough.

Frontend & Docs ​

These nginx-based services do not use environment variables. They serve static builds and require no runtime configuration.

Customizing Rate Limits ​

The default rate limits are designed for a typical self-hosted instance with a handful of users. Adjust them based on your needs:

env
RATELIMIT_GRAPHQL_PER_MIN=300    # Increase for heavy API usage
RATELIMIT_REDIRECT_PER_MIN=1200  # Increase for high-traffic shortlinks
RATELIMIT_BULK_PER_MIN=30        # Increase for frequent bulk operations
BULK_MAX_ITEMS=200               # Increase bulk operation capacity

Rate limit counters are stored in memory and reset on server restart. See Scaling for details on replacing the in-memory store with Redis.

Disabling the MCP Server ​

If you don't need the Model Context Protocol server, disable it:

env
MCP_ENABLED=false

This prevents the MCP endpoint from being mounted, reducing the server's attack surface.

Enabling Trust Proxy ​

When running behind Caddy (or any reverse proxy), Express needs to trust the proxy to correctly pick up the client's real IP address. The default TRUST_PROXY=1 trusts the single Caddy hop, which is correct and secure — clients cannot spoof their IP by prepending X-Forwarded-For entries. Only increase this value if you add additional trusted proxies in front of Caddy.

Type Checking ​

Every variable is supplied as a string in .env; the Type column above reflects how the server interprets it. Boolean variables (MCP_ENABLED, REQUIRE_AUTH_FOR_CREATE, IP_ANONYMIZE) use the string values "true"/"false". Numeric variables (PORT, TRUST_PROXY, rate limits, BULK_MAX_ITEMS, MCP_SESSION_TTL_MS, MCP_MAX_SESSIONS, SLUG_CACHE_*, IP_RETENTION_DAYS) are parsed to numbers at runtime. MCP_SCOPES is a comma-separated list.

Privacy & Data Retention ​

Click tracking stores the client IP and IP-derived geolocation per hit. To minimize the personal data you retain:

env
IP_ANONYMIZE=true      # store anonymized IPs instead of raw addresses
IP_RETENTION_DAYS=90   # purge hit records older than 90 days (daily sweep)

Per-link tracking can also be turned off entirely when creating a shortlink. See Analytics.

MCP Session Limits ​

The MCP server caps how many concurrent sessions it will hold, and reaps idle ones, to bound resource use:

env
MCP_SESSION_TTL_MS=1800000        # reap a session after 30 min idle
MCP_MAX_SESSIONS=100              # global concurrent-session cap
MCP_MAX_SESSIONS_PER_IDENTITY=10  # per user / API key

Redirect Cache ​

Hot redirects are served from an in-memory slug→target cache so most clicks never hit the database. It is invalidated automatically on update/delete:

env
SLUG_CACHE_TTL_MS=60000   # entry lifetime
SLUG_CACHE_MAX_SIZE=50000 # max cached slugs

This cache is per-process (short TTL, invalidated on write), so it stays in memory even with Redis enabled. For multi-replica deployments, see Scaling for moving the rate-limit, MCP session, and idempotency state to Redis via REDIS_URL.

Next: DNS & TLS

Released under the MIT License.