Nginx Reverse Proxy & Public SSL
Traffic Ingress Architecture
Section titled “Traffic Ingress Architecture”Once traffic passes through Cloudflare and Hetzner firewall, it hits the Hetzner server. Nginx Proxy Manager (NPM) is the primary ingress point, listening on ports 80 and 443. It reads the Host header and routes each request to the correct Docker container on proxy-network.
Nginx Proxy Manager Setup
Section titled “Nginx Proxy Manager Setup”NPM runs in its own docker-compose.yml and connects to the proxy-network alongside the web containers. This keeps internal web servers off the public interface.
# Snippet: NPM Ingress configurationservices: proxy-manager: image: 'jc21/nginx-proxy-manager:latest' container_name: nginx-proxy-manager restart: unless-stopped ports: - '80:80' # Public HTTP routing - '443:443' # Public HTTPS routing - '100.x.x.x:81:81' # OOB Management: bound to Tailscale IP only volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt networks: - proxy-networkProxy Hosts
Section titled “Proxy Hosts”Inside the NPM dashboard, public traffic is routed using Docker’s internal DNS resolver (container names) over proxy-network:
| Domain | Forward Host | Forward Port | Container |
|---|---|---|---|
pablorosi.dev | astro-site | 80 | Portfolio (Astro) |
docs.pablorosi.dev | starlight-docs | 80 | Documentation (Starlight) |
Legacy .com domains are not configured here. Their 301 redirects are handled at the Cloudflare edge — see Cloudflare DNS & Edge Routing.
SSL: Cloudflare to Origin Security
Section titled “SSL: Cloudflare to Origin Security”Traffic is encrypted in two distinct hops to ensure end-to-end security:
- Browser → Cloudflare: TLS is terminated at the Cloudflare edge.
- Cloudflare → Hetzner (Origin): TLS is terminated at NPM using a valid Let’s Encrypt certificate.
The Role of the .dev TLD (Client-Side Enforcement)
Section titled “The Role of the .dev TLD (Client-Side Enforcement)”Because pablorosi.dev uses a .dev extension (managed under Google Registry), it is hardcoded into the global HSTS Preload list built into all major web browsers.
- Zero Downgrade Attacks: Browsers automatically force HTTPS locally before sending any request. Even if a user types
http://pablorosi.dev, the browser upgrades it to HTTPS instantly. - End-to-End Guarantee: Combined with Cloudflare’s Full (strict) mode (which mandates a valid certificate on the Hetzner origin), plaintext HTTP is completely eliminated across the entire infrastructure chain—from the user’s browser all the way to the Nginx container.
Real Client IP
Section titled “Real Client IP”Because Cloudflare sits as a reverse proxy in front of the origin, NPM natively receives Cloudflare’s edge IPs rather than the user’s actual IP address.
To fix this, Trust Proxy is configured in NPM (using the real_ip module). This ensures that Nginx parses the CF-Connecting-IP (or X-Forwarded-For) header correctly, allowing access logs and rate-limiting rules to register the true client IP.
Validation
Section titled “Validation”- Both proxy hosts (
pablorosi.dev,docs.pablorosi.dev) show a valid Let’s Encrypt certificate in the NPM dashboard. curl -I https://pablorosi.devandcurl -I https://docs.pablorosi.devreturn200with no certificate warnings in the browser.- Cloudflare SSL/TLS mode is Full (strict) and the origin handshake succeeds without errors.
- NPM access logs show real client IPs (not only Cloudflare ranges) when Trust Proxy is enabled.
Proceed to Tailscale Private Admin Access for the management-plane architecture.