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.
StarkWHAT 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
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;