NGINX config help, HTTPS on NGINX, then HTTP to backend

I’m sure this is a noob question. But I’m having trouble finding the right documentation. I will continue to read the NGINX documentation, and see if I can find a suitable online course somewhere, because I don’t see my involvement with NGINX going away any time soon.

I own a domain, and have been selfhosting a small wordpress site fine for about a year. I am running proxmox, with one container for NGINX and wordpress, and another (hopefully switching to a VM with GPU passthrough soon) LXC container running Jellyfin. It’s my understanding that I can keep NGINX where it already is, do the redirect and SSL handshakes there, and run the backend unencrypted.

-Ports 80 and 443 are open in my router and forwarded to the NGINX container’s IP.
-NGINX is listening on 80 and redirecting for SSL
-I have a certificate from letsencrypt for my main site, and have recently gotten another for my subdomain (jelly.mysite.tld). I didn’t have the foresight to get a wildcard cert initially, and maybe should have revoked the existing one to replace with a single wildcard cert?
-I have an A record for the subdomain, although they both resolve to the same IP, so not sure if that’s strictly necessary. But $dig jelly.mysite.tld yields an answer.
-I’m using a separate config file in /etc/nginx/sites-available
-I’m using a modified version of the config file here. I believe this is intended to be used with NGINX on the same machine, and that’s where my problems are coming from. I did add an upstream block but it doesn’t seem to be working.

Right now I’m being served my NGINX default landing page, and HTTPS is not working. I will post each of my config files as a comment below. Thanks in advance.

#Wordpress config

server{
  root /var/www/html;
  server_name mysite.tld;
  location / {
    proxy_pass http://127.0.0.1:8080;
    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 $server_name;
    proxy_set_header X-Forwarded-Proto $scheme;
  }

    listen [::]:443 ssl ipv6only=on;
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/mysite.tld/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mysite.tld/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

server{
  listen 80;
  listen [::]:80;
  server_name mysite.tld;

  return 301 https://mysite.tld$request_uri;
}  

#Jellyfin config

server{
  listen 80;
  listen [::]:80;
  server_name jelly.mysite.tld;

  return 301 https://jelly.mysite.tld$request_uri;

  access_log /var/log/nginx/jelly.mysite.tld.access.log;
}

upstream backend {
  server 192.168.1.17:8096;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name jelly.mysite.tld;

    ## The default `client_max_body_size` is 1M, this might not be enough for some posters, etc.
    client_max_body_size 20M;

    # use a variable to store the upstream proxy
    # in this example we are using a hostname which is resolved via DNS
    # (if you aren't using DNS remove the resolver line and change the variable to point to an IP address e.g `set $jellyfin 127.0.0.1`)
    # set $jellyfin 192.168.1.17;
    # resolver 127.0.0.1 valid=30;

    ssl_certificate /etc/letsencrypt/live/jelly.mysite.tld/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/jelly.mysite.tld/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    add_header Strict-Transport-Security "max-age=31536000" always;
    ssl_trusted_certificate /etc/letsencrypt/live/jelly.mysite.tld/chain.pem;
    ssl_stapling on;
    ssl_stapling_verify on;

    # Security / XSS Mitigation Headers
    # NOTE: X-Frame-Options may cause issues with the webOS app
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "0"; # Do NOT enable. This is obsolete/dangerous
    add_header X-Content-Type-Options "nosniff";

    # COOP/COEP. Disable if you use external plugins/images/assets
    add_header Cross-Origin-Opener-Policy "same-origin" always;
    add_header Cross-Origin-Embedder-Policy "require-corp" always;
    add_header Cross-Origin-Resource-Policy "same-origin" always;

    # Permissions policy. May cause issues on some clients
    add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), battery=(), bluetooth=(), camera=(), clipboard-read=(), display-capture=(), document-domain=(), encrypted-media=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), keyboard-map=(), local-fonts=(), magnetometer=(), microphone=(), payment=(), publickey-credentials-get=(), serial=(), sync-xhr=(), usb=(), xr-spatial-tracking=()" always;

    # Tell browsers to use per-origin process isolation
    add_header Origin-Agent-Cluster "?1" always;


    # Content Security Policy
    # See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
    # Enforces https content and restricts JS/CSS to origin
    # External Javascript (such as cast_sender.js for Chromecast) must be whitelisted.
    # NOTE: The default CSP headers may cause issues with the webOS app
    #add_header Content-Security-Policy "default-src https: data: blob: http://image.tmdb.org; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com https://www.youtube.com blob:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'";

#    location = / {
#        return 302 http://$host/web/;
#        #return 302 https://$host/web/;
#    }

    location / {
        # Proxy main Jellyfin traffic
        proxy_pass http://backend;
        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 $scheme;
        proxy_set_header X-Forwarded-Protocol $scheme;
        proxy_set_header X-Forwarded-Host $http_host;

        # Disable buffering when the nginx proxy gets very resource heavy upon streaming
        proxy_buffering off;
    }

    # location block for /web - This is purely for aesthetics so /web/#!/ works instead of having to go to /web/index.html/#!/
    location = /web/ {
        # Proxy main Jellyfin traffic
        proxy_pass http://backend/web/index.html;
        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 $scheme;
        proxy_set_header X-Forwarded-Protocol $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
    }

    location /socket {
        # Proxy Jellyfin Websockets traffic
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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 $scheme;
        proxy_set_header X-Forwarded-Protocol $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
    }
}

This is using some Debian or Ubuntu thing - there’s sites-available and sites-enabled, which are meant to be symlinks to sites available, … you have those right?


Also anything useful in logs, what do you get back when you use curl -v https://jelly.yourtld.net ?

I think what you’re referring to is what HAProxy does for me so maybe that’s the kind of thing you’re looking for…?
For me, I have my home IP address and a DDNS client to update it regularly and then I have an HAProxy instance which has the certtificate on it and then passes the traffic to the backends as needed. Each backend is defined in the certificate so everything appears fine at the clients end, all trusted and all that, and it will change http to https automagically
So for instance, I have jellyfin.myhomething.com and calibre.myhomething.com and whoogle.myhomething.com which are all accessed through that one server with my one IP address as subdomains with HAProxy pointing the user to the appropriate server and acting as the frontend with the SSL cert. Then if someone types in whateverblahblah.myhomething.com then they get a 503 screen that no resource is found at this address since that one doesnt exist. I also use fail2ban on that same box and varnish on the individual backend servers in some cases but thats getting deeper with it all.
I think maybe that’s what you’re looking for? Sorry I realize it isn’t what you’re starting off with but it was easy enough to setup that I thought it worth mentioning, you could try it in another VM and if you dislike it then you can remove that one and be done with it.
There is also Nginx Proxy Manager but I haven’t used it to be able to comment on it, sounds nice and easy to use though so its worth a look to be sure…they are different but I believe both can do what you need
Since I run HAProxy I just can’t comment on that one
Jellyfin works great for me this way and I have I dunno 8 or 9 other services running alongside it altogether

Yes, I have symlinks to /sites-enabled. I made backups, and started updating/pushing and eventually I got it to work just as expected. I think the upstream directive was the magic bullet, but I’m really unsure what minor change was the one that ultimately made it work.

I debated editing the post to tell everyone to disregard, but I know I’m not following best practices and wanted suggestions. I know I should be able to get it all into one config file ultimately.

But the deep dive courses that the nginx organization offers are $1500. And there’s a lack of information on YouTube and other places unless your want a very cursory look at it. The documentation is pretty dry too.

1 Like

It makes you wonder about their incentive structure … e.g. write poor docs, and get rewarded with $1500 per person needing help.

I’ve personally migrated most of “my stuff” from nginx to caddy, and use nginx only for steering connections based on SNI. I realize nginx might be more performant, but I like that caddy has built-in cert renewal and reloading.

1 Like

Honestly just figured out a very similar problem here, but I was using SWAG which is an integrated stack that had a very different setup, I ended up basically copping a default Wordpress config for something different.

A +1 for Caddyserver. It’s my go-to for every deployment that doesn’t require really intricate URL/header rewrite rules.

if your still stuck have your config files print all the variable names and there values as they happen.
you can then see what has worked or what failed to return the correct value.