Skip to content

Troubleshooting

This page covers common issues you may encounter when self-hosting lnkify, along with diagnostic steps and solutions.


1. TLS Certificate Failed

Symptoms: Caddy logs show TLS errors, browsers show certificate warnings, the site is inaccessible over HTTPS.

Causes:

  • Ports 80 and/or 443 are not reachable from the internet.
  • DNS records have not propagated.
  • A firewall is blocking inbound traffic.

Diagnosis:

bash
# Check if ports are listening
docker compose ps caddy

# Check Caddy logs for ACME errors
docker compose logs caddy | grep -i "acme\|tls\|error"

# Verify DNS points to your server
dig lnkify.io +short

# Test port reachability from an external host
curl -I http://lnkify.io

Solutions:

  • Ensure ports 80 and 443 are open on the host firewall and any network firewalls (cloud provider security groups).
  • Verify DNS A records point to the correct IP. Wait for DNS propagation if records were recently changed.
  • Restart Caddy after DNS is confirmed: docker compose restart caddy.
  • If you've requested many certificates recently, Let's Encrypt rate limits may apply. Wait an hour and retry.

2. Server Can't Connect to Database

Symptoms: Server logs show ECONNREFUSED, Connection refused, or Prisma connection errors. Server restarts repeatedly.

Causes:

  • The db service is not healthy.
  • DATABASE_URL is misconfigured.
  • The server started before the database healthcheck passed (unlikely with condition: service_healthy, but possible if the healthcheck is misconfigured).

Diagnosis:

bash
# Check database health
docker compose ps db

# Check database logs
docker compose logs db

# Test connectivity from server
docker compose exec server sh -c "nc -zv db 5432"

# Verify DATABASE_URL
docker compose exec server sh -c "echo \$DATABASE_URL"

Solutions:

  • Wait for the database to become healthy. The server depends on condition: service_healthy, so it won't start until pg_isready passes.
  • Verify DATABASE_URL matches the database credentials in .env:
    DATABASE_URL=postgresql://postgres:password@db:5432/lnkify
    The host must be db (the Docker Compose service name), not localhost.
  • Check that POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB are set correctly in .env.

3. 401 Unauthorized on MCP

Symptoms: MCP clients receive 401 Unauthorized when connecting to https://mcp.lnkify.io.

Causes:

  • Missing x-api-key header in MCP requests.
  • The API key format is incorrect.
  • The API key has been revoked.

Diagnosis:

bash
# Test MCP endpoint with a valid API key
curl -H "x-api-key: lf_live_abc123..." https://mcp.lnkify.io/mcp

# Check server logs for auth failures
docker compose logs server | grep "401\|unauthorized\|api.key"

Solutions:

  • Ensure your MCP client is configured to send the x-api-key header. See MCP Authentication.
  • Verify the API key is active in the lnkify dashboard under API Keys.
  • Create a new API key if the existing one has been revoked.
  • The key format should be lf_live_<random_string>.

4. Rate Limited (429)

Symptoms: Requests return 429 Too Many Requests with RateLimit-* response headers.

Causes:

  • You've exceeded the configured rate limits.
  • Rate limits are too restrictive for your use case.

Diagnosis:

bash
# Check response headers for rate limit info
curl -vI https://lnkify.io/graphql

# Look for headers: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset

Solutions:

  • Wait for the rate limit window to reset (typically 1 minute).
  • Increase rate limits in .env:
    env
    RATELIMIT_GRAPHQL_PER_MIN=300
    RATELIMIT_REDIRECT_PER_MIN=1200
    RATELIMIT_BULK_PER_MIN=30
  • Restart the server to reset all in-memory rate limit counters: docker compose restart server.
  • Rate limit counters are per-identity (for GraphQL) or per-IP (for redirects). Ensure TRUST_PROXY=1 (the single Caddy hop) so real client IPs are used — never set it to true, which trusts the entire client-supplied X-Forwarded-For chain.

5. Container Keeps Restarting

Symptoms: docker compose ps shows a service continuously restarting.

Causes:

  • Missing required environment variables.
  • A configuration file is invalid.
  • The service crashes immediately on startup.

Diagnosis:

bash
# Watch logs for the failing service
docker compose logs -f server

# Check restart count
docker compose ps -a

# Inspect the container exit code
docker inspect lnkify_server_1 | grep -A5 State

Solutions:

  • Check for missing required variables: JWT_SECRET, DATABASE_URL, APIKEY_PEPPER are mandatory.
  • Verify the .env file is present and not empty.
  • For the Caddy service, check the Caddyfile syntax: docker compose run --rm caddy caddy validate --config /etc/caddy/Caddyfile.
  • For the server, run it interactively to see startup errors:
    bash
    docker compose run --rm server

Symptoms: Visiting a shortlink (e.g., https://lnkify.io/abc123) does not redirect. You see a 404 or the frontend page.

Causes:

  • The slug does not exist in the database.
  • Caddy is routing the request to the frontend instead of the server.
  • The server is not running.

Diagnosis:

bash
# Test the redirect directly against the server
docker compose exec server sh -c "curl -v http://localhost:4000/abc123"

# Check Caddy routing — verify the Caddyfile catch-all is present
docker compose exec caddy cat /etc/caddy/Caddyfile

Solutions:

  • Verify the slug exists in the database:
    bash
    docker compose exec db psql -U postgres -d lnkify -c "SELECT id, lnkify, target FROM \"Lnkify\" WHERE lnkify = 'abc123';"
  • Ensure the Caddyfile catch-all handle { reverse_proxy server:4000 } is present at the end of the lnkify.io block.
  • Check that the server is running and healthy: docker compose ps server.

7. Analytics Not Showing Location Data

Symptoms: Click analytics show no city/country data. All clicks appear without geographic information.

Causes:

  • IPREGISTRY_API_KEY is not set.
  • The IP registry API key is invalid or has expired.
  • The ipregistry.co service is unreachable.

Diagnosis:

bash
# Check if the variable is set
docker compose exec server sh -c "echo \$IPREGISTRY_API_KEY"

# Test the API key manually
curl "https://api.ipregistry.co/8.8.8.8?key=$(docker compose exec -T server sh -c 'echo $IPREGISTRY_API_KEY')"

Solutions:

  • Get a free API key from ipregistry.co.
  • Add it to .env: IPREGISTRY_API_KEY=ir_...
  • Restart the server: docker compose restart server.
  • Note: Location data is only available for new clicks after the key is set.

8. MCP Sessions Dropping

Symptoms: MCP clients disconnect intermittently and must reconnect.

Causes:

  • Server restart clears the in-memory session map.
  • Single-replica limitation — sessions are not shared.

Solutions:

  • This is expected behavior for the single-replica architecture. See Scaling for details.
  • If using multiple replicas, configure sticky sessions in Caddy (see Scaling).
  • MCP clients should implement reconnection logic. The session drop is brief and clients can reconnect after restart.

Useful Diagnostic Commands

bash
# Service status
docker compose ps

# Follow all logs
docker compose logs -f

# Follow a specific service's logs
docker compose logs -f server
docker compose logs -f caddy
docker compose logs -f db

# Enter a service container
docker compose exec server sh
docker compose exec db sh

# Check if .env is loaded correctly
docker compose config

# Check resource usage
docker stats

# Restart all services
docker compose restart

# Full restart (stop, recreate, start)
docker compose up -d --force-recreate

Port Conflicts

If ports 80 or 443 are already in use on the host:

bash
# Check what's using port 80
sudo lsof -i :80

# Check what's using port 443
sudo lsof -i :443

Common culprits: nginx, Apache, another Caddy instance, or a previous lnkify installation. Stop any conflicting services before starting lnkify.

Getting Help

If you encounter an issue not covered here:

  1. Search existing issues: GitHub Issues
  2. Check discussions: GitHub Discussions
  3. Open a new issue: Include docker compose logs, .env (with secrets redacted), and steps to reproduce.

Next: Architecture

Released under the MIT License.