There is a version of this setup that is properly architected. It has separate hosts, a container orchestrator, and a monitoring stack that pages someone at 3am. It is not this one.
This one is a single machine, a single nginx config, and a stubborn refusal to pay for anything I can run myself.
The reverse proxy is the whole trick
Almost every self-hosting problem I have had turned out to be a proxy problem wearing a costume. An app “doesn’t load” — actually its UI loads fine and its websocket doesn’t, because the upgrade header never made it through:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
Without that map, the *arr apps render perfectly and then sit there, frozen in time, never updating the queue. It looks like the app is broken. It isn’t.
Trailing slashes are load-bearing
proxy_pass http://127.0.0.1:7878; and proxy_pass http://127.0.0.1:7878/;
are different instructions, and the difference is whether nginx strips the
location prefix before forwarding. If the app has a URL-base setting, it wants
the prefix left intact. If it doesn’t, it wants it gone. Getting this backwards
produces a 404 from software that is running perfectly.
Certificates fail quietly
The best failure I have had: a blanket redirect from port 80 to HTTPS, sitting above the ACME challenge location. Renewal had been failing for months without a single visible symptom, because the certificate was still valid. The fix is ordering — the challenge location has to come first:
location ^~ /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://$host$request_uri; }
Worth it?
Yes, but not for the reasons I expected. The media stack is nice. The actual value is that every layer between a browser and a process is now something I have personally broken and fixed at least once.