Skip to content

Cybersecurity

Reverse proxy security: TLS, headers, rate limits and what not to expose

How to harden Nginx, Caddy or Traefik as the front door of a self-hosted setup: HTTPS, security headers, rate limiting, request size limits, hiding versions and protecting admin paths.

By · Published · 3 min read

Short answer: a reverse proxy is the one door into your services, so harden it first. Serve only HTTPS with modern TLS, redirect HTTP, add security headers, rate-limit sensitive paths, cap request sizes and timeouts, hide version banners, keep admin paths off the public internet, and make the backend services listen only on localhost or a private network.

If you are new to the idea, start with what a reverse proxy is. This post assumes you already have one.

1. Force HTTPS

Redirect all HTTP to HTTPS. Caddy does it automatically. In Nginx:

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

Use TLS 1.2 and 1.3 only. Mozilla's configuration generator gives current cipher settings for your server and your client compatibility needs. Let's Encrypt certificates are free and can renew on their own. Add alerts for expiry anyway, because renewal failures are quiet.

2. Add security headers

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
  • HSTS tells browsers to use HTTPS only. Start with a short max-age, confirm every subdomain works, then raise it.
  • nosniff stops browsers guessing content types.
  • X-Frame-Options or CSP frame-ancestors prevents clickjacking.
  • Content-Security-Policy is the strongest and the easiest to get wrong. Test it before enforcing, because a strict policy can break an app completely, as in CSP and Next.js hydration. Begin with Content-Security-Policy-Report-Only.

3. Rate-limit what attackers hit

limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;

location /login {
    limit_req zone=login burst=10 nodelay;
    proxy_pass http://127.0.0.1:3000;
}

Login, password reset, signup, search and anything that sends email or SMS deserve limits. If you sit behind another proxy or a CDN, make sure the client IP you limit on is the real one and not the CDN's, otherwise you rate-limit everyone together.

4. Cap sizes and timeouts

client_max_body_size 10m;
client_body_timeout 15s;
client_header_timeout 15s;
keepalive_timeout 30s;

This blunts slow-connection attacks and oversized uploads. Raise the body limit only on the paths that need it.

5. Reduce information leaks

  • Turn off version banners: server_tokens off; in Nginx.
  • Return generic error pages, not stack traces from the backend.
  • Do not expose /server-status, /metrics, /.git, /.env or backup files. Test them from outside.

6. Protect admin paths

Restrict dashboards by IP or VPN, and add authentication at the proxy as a second lock.

location /admin/ {
    allow 10.8.0.0/24;     # VPN range
    deny  all;
    proxy_pass http://127.0.0.1:3000;
}

7. Make the backends private

The proxy protects nothing if the app is also reachable directly on its own port. Bind services to 127.0.0.1 or a private Docker network. With Docker, ports: ["127.0.0.1:3000:3000"] is the safe form, because plain 3000:3000 publishes to all interfaces and bypasses ufw.

8. Forward the right headers

Pass X-Forwarded-For, X-Forwarded-Proto and Host to the app, and configure the app to trust them only from the proxy. An app that trusts X-Forwarded-For from anyone lets attackers fake their IP.

9. Log and watch

Keep access logs and review them for scanners hitting odd paths, repeated 401s and bursts of 5xx. Ship them off the box. Combine with the rest of the hardening checklist.

References

Author

Raktim Ranjit is a software engineer and the founder of NodeDR Infotech. He builds and maintains the software described here.

Have something in mind?

Let’s build something useful.

Tell me about the idea, product, or workflow you’re working through.

Tap to say hello