Skip to main content

NGINX VHOST

nginx-vhost.conf

Cloudflare-Flexible-safe origin vhost: origin security header stack, ACME http-01 passthrough, per-tenant logs, and deliberately NO HTTP->HTTPS redirect to avoid the Flexible-SSL loop.

Stark avatarStark

WHAT THIS PATTERN TEACHES

Why an origin-level HTTP->HTTPS redirect causes ERR_TOO_MANY_REDIRECTS on a Cloudflare Flexible zone (the edge talks plain HTTP to the origin), so the redirect is omitted. Where http{}-only directives (limit_req_zone, the upgrade map) must live vs. where they apply, how to keep /.well-known/acme-challenge/ reachable on :80, and why the security headers go at the origin with `always` so the posture survives a removed Cloudflare rule.

WHEN TO USE THIS

Cloudflare zone SSL mode = Flexible and the origin speaks HTTP only. Copy to /etc/nginx/sites-available/<tenant>.conf, replace @@SERVER_NAME@@/@@UPSTREAM@@/@@TENANT@@. Use a TLS-terminating vhost with the 301 instead when the zone is Full/Full-strict.

AT A GLANCE

# Cloudflare Flexible: edge does HTTPS, this hop is HTTP.
# NO `return 301 https://...` here — it would loop.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location ^~ /.well-known/acme-challenge/ { root /var/www/acme; try_files $uri =404; }
proxy_set_header X-Forwarded-Proto https;

FRAMEWORK IMPLEMENTATIONS

nginx
upstream @@TENANT@@_origin {
    server @@UPSTREAM@@;
    keepalive 32;
}

server {
    listen 80;
    listen [::]:80;
    server_name @@SERVER_NAME@@;

    # Per-tenant logs so one tenant's traffic never contaminates another's trail.
    access_log /var/log/nginx/@@TENANT@@.access.log combined;
    error_log  /var/log/nginx/@@TENANT@@.error.log warn;

    # ACME http-01: Let's Encrypt fetches this over plain HTTP on :80.
    # MUST NOT be redirected or proxied, or issuance/renewal fails.
    location ^~ /.well-known/acme-challenge/ {
        default_type "text/plain";
        root /var/www/acme;
        try_files $uri =404;
← All Patterns