Skip to content
Portfolio

Nginx Reverse Proxy & Public SSL

Once traffic passes through Cloudflare, 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.


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 configuration
services:
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-network

Inside the NPM dashboard, public traffic is routed using Docker’s internal DNS resolver (container names) over proxy-network:

DomainForward HostForward PortContainer
pablorosi.devastro-site80Portfolio (Astro)
docs.pablorosi.devstarlight-docs80Documentation (Starlight)

Legacy .com domains are not configured here. Their 301 redirects are handled at the Cloudflare edge — see Cloudflare DNS & Edge Routing.


Traffic is encrypted in two hops:

  1. Browser → Cloudflare: TLS terminated at the Cloudflare edge.
  2. Cloudflare → Hetzner: TLS terminated at NPM using a Let’s Encrypt certificate.

Cloudflare SSL/TLS mode is set to Full (strict). NPM must present a valid origin certificate; self-signed certs are rejected. Let’s Encrypt certificates are issued per domain inside NPM.


Because Cloudflare sits in front of the origin, NPM receives Cloudflare proxy IPs unless configured otherwise. Enable Trust Proxy in NPM (or equivalent forwarded-header settings) so logs and access rules see the real client IP from CF-Connecting-IP / X-Forwarded-For.


  • Both proxy hosts (pablorosi.dev, docs.pablorosi.dev) show a valid Let’s Encrypt certificate in the NPM dashboard.
  • curl -I https://pablorosi.dev and curl -I https://docs.pablorosi.dev return 200 with 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.