# Main process configuration worker_processes 1; events { worker_connections 1024; } http { geo $frontend_whitelist { default 1; 127.0.0.1 1; 172.20.0.0/16 1; # Frontend Docker subnet 5.75.153.161 1; # Grafana or monitoring 167.235.254.4 1; # Ansible server IP } geo $backend_whitelist { default 1; 127.0.0.1 1; 172.19.0.0/16 1; # Backend Docker subnet 5.75.153.161 1; # Grafana or monitoring 167.235.254.4 1; # Ansible server IP } # These settings ensure that $remote_addr reflects the real client IP forwarded by https-portal, which is needed for your allow rules to work correctly # Recommended for resolving client IP behind proxy # Docker networks where both frontend and backend containers communicate through NGINX. # To avoid potential misclassification of real client IPs from backend routes. # The set_real_ip_from directive doesn’t allow access — it just instructs NGINX to trust the X-Forwarded-For header from those IPs. set_real_ip_from 172.20.0.0/16; # Replace with your Docker network subnet (matches your `frontend` network) set_real_ip_from 172.19.0.0/16; # Replace with your Docker network subnet (matches your `backend` network) real_ip_header X-Forwarded-For; real_ip_recursive on; resolver 127.0.0.11 valid=10s; resolver_timeout 5s; upstream phoenix_system_cluster { zone phoenix_system_cluster 64k; least_conn; server phoenix-system:3000 resolve fail_timeout=1s max_fails=0; # ADD_SYSTEM_SERVERS_HERE } upstream phoenix_worker_cluster { zone phoenix_worker_cluster 64k; least_conn; server phoenix-worker:3001 resolve fail_timeout=1s max_fails=0; # ADD_WORKER_SERVERS_HERE } server_tokens off; # Disable NGINX version tokens to avoid leaking NGINX version. # File handling & upload limits sendfile on; client_max_body_size 64m; # Prevent warning when setting many proxy headers, like we do proxy_headers_hash_max_size 1024; proxy_headers_hash_bucket_size 128; # Gzip compression (for better bandwidth efficiency) gzip on; gzip_min_length 1000; gzip_proxied expired no-cache no-store private auth; gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; # Trust the protocol from upstream proxy/load balancer map $http_x_forwarded_proto $forwarded_proto { default $scheme; https https; http http; } # File types and default mime type include /etc/nginx/mime.types; default_type application/octet-stream; # 🧩 Logs map $request_uri $loggable { default 1; ~^/stub_status 0; ~^/health/system 0; ~^/health/worker 0; } log_format main_with_realip '$remote_addr - $realip_remote_addr [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"'; log_format json_compatible escape=json '{' '"time":"$time_iso8601",' '"remote_addr":"$remote_addr",' '"proxy_addr":"$proxy_protocol_addr",' '"x_forwarded_for":"$http_x_forwarded_for",' '"request_method":"$request_method",' '"request_uri":"$request_uri",' '"status":$status,' '"body_bytes_sent":$body_bytes_sent,' '"request_time":$request_time,' '"upstream_response_time":"$upstream_response_time",' '"http_referer":"$http_referer",' '"http_user_agent":"$http_user_agent",' '"host":"$host",' '"realip":"$realip_remote_addr"' '}'; access_log /var/log/nginx/access_json.log json_compatible if=$loggable; # JSON format for Loki access_log /var/log/nginx/access.log main_with_realip if=$loggable; # End of logs ################################################################## # 🧩 HTTP Server Block ################################################################## server { listen 80; server_name _; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; root /usr/share/nginx/html; index index.html index.htm; # Frontend SPA fallback location / { try_files $uri $uri/ /index.html; } # Backend API routes location /backend-api/ { proxy_pass http://phoenix_system_cluster/; # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } location /admin-api { proxy_pass http://phoenix_system_cluster/admin-api; # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } location /remote-assets { proxy_pass http://phoenix_system_cluster/remote-assets; # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } location /sti { proxy_pass http://phoenix_system_cluster/sti; # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } # WebSocket support location /ws { proxy_pass http://phoenix_system_cluster/graphql; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } # Reverse proxy for pgAdmin (subpath support) include /etc/nginx/includes/*.conf; # Health check endpoints -> used by the health check exporter location /health/system { proxy_pass http://phoenix_system_cluster/health; # Secure the health check endpoint if ($backend_whitelist = 0) { return 403; } # End of security # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } # location /health/system/metrics { # proxy_pass http://phoenix_system_cluster/health/metrics; # # Secure the health check endpoint # # if ($backend_whitelist = 0) { # # return 403; # # } # # End of security # # Include headers for proxying # proxy_set_header Host $host; # proxy_set_header X-Real-IP $remote_addr; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header X-Forwarded-Proto $forwarded_proto; # # End of headers # } location /health/worker { proxy_pass http://phoenix_worker_cluster/health; # Secure the health check endpoint if ($backend_whitelist = 0) { return 403; } # End of security # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } # location /health/worker/metrics { # proxy_pass http://phoenix_worker_cluster/health/metrics; # # Secure the health check endpoint # # if ($backend_whitelist = 0) { # # return 403; # # } # # End of security # # Include headers for proxying # proxy_set_header Host $host; # proxy_set_header X-Real-IP $remote_addr; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header X-Forwarded-Proto $forwarded_proto; # # End of headers # } location /stub_status { stub_status; # Secure the stub status endpoint if ($frontend_whitelist = 0) { return 403; } # End of security } } ################################################################## # 🔐 HTTPS Server Block ################################################################## server { listen 443 ssl; http2 on; server_name _; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; ssl_certificate /etc/nginx/external-certificate/certificate.crt; ssl_certificate_key /etc/nginx/external-certificate/certificate.key; root /usr/share/nginx/html; index index.html index.htm; location / { try_files $uri $uri/ /index.html; } # Secure API routes location /backend-api/ { proxy_pass http://phoenix_system_cluster/; # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } location /admin-api { proxy_pass http://phoenix_system_cluster/admin-api; # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } location /remote-assets { proxy_pass http://phoenix_system_cluster/remote-assets; # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } location /sti { proxy_pass http://phoenix_system_cluster/sti; # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } location /ws { proxy_pass http://phoenix_system_cluster/graphql; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } # Reverse proxy for pgAdmin (subpath support) include /etc/nginx/includes/*.conf; location /health/system { proxy_pass http://phoenix_system_cluster/health; # Secure the health check endpoint if ($backend_whitelist = 0) { return 403; } # End of security # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } location /health/worker { proxy_pass http://phoenix_worker_cluster/health; # Secure the health check endpoint if ($backend_whitelist = 0) { return 403; } # End of security # Include headers for proxying proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $forwarded_proto; # End of headers } location /stub_status { stub_status; # Secure the stub status endpoint if ($frontend_whitelist = 0) { return 403; } # End of security } } }