# Hostnames that are reachable from the public internet. The operator console # (/admin) must NEVER be served on these — only the marketing site and the # customer portal are public; Proxmox hosts and /admin stay on the private # network / VPN. # # Deliberately a denylist, not an allowlist: a typo in an allowlist would lock # the operators out of their own console. The app-level allowlist (ADMIN_HOSTS, # see config/admin_access.php) is the strict layer — this one guarantees that a # request arriving on a known-public hostname never even reaches PHP. map $host $admin_host_is_public { default 0; www.dev.clupilot.com 1; app.dev.clupilot.com 1; api.dev.clupilot.com 1; ws.dev.clupilot.com 1; www.clupilot.com 1; app.clupilot.com 1; api.clupilot.com 1; ws.clupilot.com 1; } server { listen 80 default_server; server_name _; root /var/www/html/public; index index.php; charset utf-8; client_max_body_size 64M; # ^~ beats the regex locations, so this decides before PHP is reached. The # try_files rewrite to /index.php re-runs location matching, so the console # still executes normally on a permitted host. # # This only covers direct hits: Livewire posts component actions to # /livewire/update, which nginx cannot attribute to /admin. That case is # handled in the app (RestrictAdminHost is registered as Livewire-persistent # middleware) — do not treat this block as the only control. location ^~ /admin { if ($admin_host_is_public) { return 404; } try_files $uri $uri/ /index.php?$query_string; } location / { try_files $uri $uri/ /index.php?$query_string; } location = /favicon.ico { access_log off; log_not_found off; } # robots.txt is generated by the app (it changes with the site's visibility), # so it must NOT be short-circuited to a file on disk. location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $realpath_root; } location ~ /\.(?!well-known).* { deny all; } }