Multi-site on one VPS stays sane when nginx is the only process publishing 80/443 and every app container sits on a private compose network. Prefer one server_name block per public hostname over path-based sharing when the stacks disagree on base paths; point each proxy_pass at the service DNS name on an unpublished port so a recreate never forces you to chase ephemeral container IPs on the host. Mount TLS material read-only into the proxy, keep ACME HTTP-01 on the plain listener for that same server_name, and leave app ports unpublished so a forgotten -p never bypasses TLS or access rules.
After a reload I hit each public hostname once and confirm the leaf SAN matches, that the first plain request becomes a single HTTPS redirect to the canonical host, and that Host plus X-Forwarded-Proto reach the upstream — missing those makes WordPress or similar emit http redirects with the internal service name. From inside the nginx container, curl each backend by service name, then the same path through the matching vhost; a connect failure is network membership, while a trailing-slash join mismatch usually shows up as a 404 that only appears on the public name.
CDN Edge Ops
Multi-site on one VPS stays sane when nginx is the only process publishing 80/443 and every app container sits on a private compose network. Prefer one server_name block per public hostname over path-based sharing when the stacks disagree on base paths; point each proxy_pass at the service DNS name on an unpublished port so a recreate never forces you to chase ephemeral container IPs on the host. Mount TLS material read-only into the proxy, keep ACME HTTP-01 on the plain listener for that same server_name, and leave app ports unpublished so a forgotten -p never bypasses TLS or access rules.
After a reload I hit each public hostname once and confirm the leaf SAN matches, that the first plain request becomes a single HTTPS redirect to the canonical host, and that Host plus X-Forwarded-Proto reach the upstream — missing those makes WordPress or similar emit http redirects with the internal service name. From inside the nginx container, curl each backend by service name, then the same path through the matching vhost; a connect failure is network membership, while a trailing-slash join mismatch usually shows up as a 404 that only appears on the public name.