HelloJohn / docs
Architecture

Networking & Security

HelloJohn's network security model — rate limiting, CORS configuration, TLS requirements, security headers, and IP allowlisting.

TLS / HTTPS

All production HelloJohn instances must run behind TLS. The server itself does not terminate TLS — run it behind a reverse proxy (nginx, Caddy, Traefik, Cloudflare) that handles SSL.

nginx.conf (example)
server {
    listen 443 ssl;
    server_name auth.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/auth.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/auth.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://localhost:3100;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
    }
}

Set X-Forwarded-Proto: https in your reverse proxy. HelloJohn uses this header to determine if the request is over HTTPS. Without it, some security checks may behave incorrectly.

CORS

Configure allowed origins per-tenant. Requests from unlisted origins are rejected with 403 Forbidden.

.env
# Comma-separated list of allowed origins
HELLOJOHN_ALLOWED_ORIGINS=https://app.yourdomain.com,https://admin.yourdomain.com

Or per-tenant via the API:

curl -X PATCH https://your-instance.com/cp/v1/tenants/tnt_01HABCDEF \
  -H "Authorization: Bearer {cp_admin_token}" \
  -d '{"allowed_origins": ["https://app.yourdomain.com"]}'

CORS preflight: HelloJohn handles OPTIONS preflight requests automatically. No additional configuration needed for standard CORS headers.

Rate limiting

HelloJohn applies rate limits per IP and per client to protect against brute force and abuse.

Default limits

EndpointLimitWindow
POST /v1/auth/sign-in10 requests1 minute per IP
POST /v1/auth/sign-up5 requests1 minute per IP
POST /v1/auth/refresh30 requests1 minute per session
POST /v1/mfa/verify5 requests5 minutes per user
POST /v1/auth/forgot-password3 requests10 minutes per IP
All other endpoints100 requests1 minute per client key

Rate limit response

When a rate limit is exceeded, HelloJohn returns:

HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700000045

{
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Please try again in 45 seconds.",
  "retry_after": 45
}

Configuring rate limits

.env
# Disable rate limiting (development only)
HELLOJOHN_RATE_LIMIT_ENABLED=false

# Redis for distributed rate limiting (required for multi-instance)
HELLOJOHN_REDIS_URL=redis://localhost:6379

For multi-instance deployments, configure Redis. Without Redis, rate limits are tracked in-memory per instance and won't be coordinated across replicas.

Security headers

HelloJohn automatically sets the following response headers:

HeaderValuePurpose
X-Content-Type-OptionsnosniffPrevent MIME sniffing
X-Frame-OptionsDENYPrevent clickjacking
Referrer-Policystrict-origin-when-cross-originLimit referrer leakage
Strict-Transport-Securitymax-age=63072000; includeSubDomainsForce HTTPS
Content-Security-PolicyConfigurablePrevent XSS

You can add or override headers in your reverse proxy:

add_header X-Custom-Header "value" always;

IP allowlisting

Restrict access to the Control Plane API to specific IP ranges:

.env
# Only allow these IPs to access /cp/* endpoints
HELLOJOHN_CP_ALLOWED_IPS=10.0.0.0/8,203.0.113.0/24

For the Data Plane, IP allowlisting is not recommended (your users' IPs are unknown). Use it only for server-to-server API key endpoints.

Trusted proxies

If running behind a load balancer, configure trusted proxy IPs so HelloJohn reads X-Forwarded-For for the real client IP:

.env
# Trust these proxies for X-Forwarded-For
HELLOJOHN_TRUSTED_PROXIES=10.0.0.0/8

Without this, all requests appear to come from the proxy IP, breaking per-IP rate limiting.

Ports

PortDefaultDescription
HTTP3100Main API server (HTTP, not HTTPS)
Metrics9090Prometheus metrics endpoint
Health3100/healthLiveness/readiness probe

Health checks

# Liveness probe
GET /health
 200 OK { "status": "ok" }

# Readiness probe (checks DB connection)
GET /health/ready
 200 OK { "status": "ready", "db": "ok", "redis": "ok" }
 503 Service Unavailable { "status": "not_ready", "db": "error" }

Next steps

On this page