2023-08-18, 09:43 PM
I'm trying to set up a VPS such that I can allow my parents to use their Roku to load / play things from my Jellyfin server. The VPS is running an nginx server with a reverse proxy setup as per the Jellyfin documentation, as far as I can tell, but I think some parts are misconfigured, and there's some differences between how I have my system set up versus the official documentation, and so I'm not really sure how to get it all working correctly.
The general idea that I'm going for is:
internet client -> https to my VPS -> proxy connection via SSH tunnel -> my synology -> docker container running Jellyfin
My starting point for this was this thread on Reddit: https://www.reddit.com/r/jellyfin/commen..._to_allow/
My Environment:
Local:
Jellyfin via Docker, running on a Synology DS920+ which handles hardware transcoding and has all the local storage for media.
VPS:
Linode VPS, static IP, nginx installed and configured, Let's Encrypt SSL cert is OK, custom DNS and domain name all set up (using a subdomain setup, ie jellyfin.cloud.mydomain.com), secured with only SSH Certificate login and Fail2Ban set up / secured.
Where I went off script / tried customizing things:
I can't get the Docker container to handle the SSH Tunnel configuration from the Jellyfin container to the VPS, so I figured I could do that from the Synology itself via command line for testing, then later set it up via scheduled task to make sure that the tunnel is set up and running on reboot, and/or resets daily at some time, so that it's sure to be up and running. I haven't been able to confirm that this is actually working or not, but it appears to be?
Configuration stuff:
VPS:
/etc/nginx/conf.d/jellyfin.conf
On the Synology via command line, I'm running the below command to establish the ssh tunnel:
I can see that the connection is established on the VPS:
[vpshost]$ sudo netstat -at
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 localhost:8096 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:https 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:http 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN
tcp 0 288 cloud.mydomain.tld:ssh redacted.ip.addr:15100 ESTABLISHED
tcp 0 0 cloud.mydomain.tld:ssh redacted.ip.addr:54494 ESTABLISHED
tcp6 0 0 [::]:https [::]:* LISTEN
tcp6 0 0 [::]:http [::]:* LISTEN
tcp6 0 0 [::]:ssh [::]:* LISTEN
However, when I try to load my jellyfin instance, I am getting a 502 Bad Gateway error from the web server:
https://jellyfin.cloud.mydomain.tld/ forwards to https://jellyfin.cloud.mydomain.tld/web/ ---> returns 502 Bad Gateway nginx/1.20.1
/var/log/nginx/error.log
2023/08/18 14:02:40 [crit] 699#699: connect() to 23.214.95.221:80 failed (13: Permission denied) while requesting certificate status, responder: r3.o.lencr.org, peer: 23.214.95.221:80, certificate: "/etc/letsencrypt/live/jellyfin.cloud.mydomain.tld/fullchain.pem"
2023/08/18 14:02:40 [crit] 699#699: connect() to 23.214.95.212:80 failed (13: Permission denied) while requesting certificate status, responder: r3.o.lencr.org, peer: 23.214.95.212:80, certificate: "/etc/letsencrypt/live/jellyfin.cloud.mydomain.tld/fullchain.pem"
2023/08/18 14:02:40 [crit] 699#699: connect() to [2600:1406:5600:3::17d6:5fdd]:80 failed (13: Permission denied) while requesting certificate status, responder: r3.o.lencr.org, peer: [2600:1406:5600:3::17d6:5fdd]:80, certificate: "/etc/letsencrypt/live/jellyfin.cloud.mydomain.tld/fullchain.pem"
2023/08/18 14:02:40 [crit] 699#699: connect() to [2600:1406:5600:3::17d6:5fd4]:80 failed (13: Permission denied) while requesting certificate status, responder: r3.o.lencr.org, peer: [2600:1406:5600:3::17d6:5fd4]:80, certificate: "/etc/letsencrypt/live/jellyfin.cloud.mydomain.tld/fullchain.pem"
2023/08/18 14:02:41 [crit] 699#699: *1 connect() to 127.0.0.1:8096 failed (13: Permission denied) while connecting to upstream, client: 146.70.174.92, server: jellyfin.cloud.mydomain.tld, request: "GET /web/ HTTP/2.0", upstream: "http://127.0.0.1:8096/web/index.html", host: "jellyfin.cloud.mydomain.tld"
2023/08/18 14:02:41 [crit] 699#699: *1 connect() to 127.0.0.1:8096 failed (13: Permission denied) while connecting to upstream, client: 146.70.174.92, server: jellyfin.cloud.mydomain.tld, request: "GET /favicon.ico HTTP/2.0", upstream: "http://127.0.0.1:8096/favicon.ico", host: "jellyfin.cloud.mydomain.tld", referrer: "https://jellyfin.cloud.mydomain.tld/web/"
Potential problems that I'm considering, but don't know how to troubleshoot:
NOTE: I've replaced all instances of my actual domain name with "mydomain.tld"
Thank you reading and for any suggestions or troubleshooting steps! I'd have put this all in the Jellyfin Reddit thread on the subject, but it appears that that's no longer possible. I'm hoping that this setup isn't too weird, since I'd really like to avoid having to forward ports from my home network.
The general idea that I'm going for is:
internet client -> https to my VPS -> proxy connection via SSH tunnel -> my synology -> docker container running Jellyfin
My starting point for this was this thread on Reddit: https://www.reddit.com/r/jellyfin/commen..._to_allow/
My Environment:
Local:
Jellyfin via Docker, running on a Synology DS920+ which handles hardware transcoding and has all the local storage for media.
VPS:
Linode VPS, static IP, nginx installed and configured, Let's Encrypt SSL cert is OK, custom DNS and domain name all set up (using a subdomain setup, ie jellyfin.cloud.mydomain.com), secured with only SSH Certificate login and Fail2Ban set up / secured.
Where I went off script / tried customizing things:
I can't get the Docker container to handle the SSH Tunnel configuration from the Jellyfin container to the VPS, so I figured I could do that from the Synology itself via command line for testing, then later set it up via scheduled task to make sure that the tunnel is set up and running on reboot, and/or resets daily at some time, so that it's sure to be up and running. I haven't been able to confirm that this is actually working or not, but it appears to be?
Configuration stuff:
VPS:
/etc/nginx/conf.d/jellyfin.conf
Code:
# jellyfin configuration, taken from https://jellyfin.org/docs/general/networking/nginx/
# uncomment sections once https is set up.
server {
if ($host = jellyfin.cloud.mydomain.tld) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name jellyfin.cloud.mydomain.tld;
# Uncomment to redirect HTTP to HTTPS:
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name jellyfin.cloud.mydomain.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 127.0.0.1;
#resolver 127.0.0.1 valid=30;
ssl_certificate /etc/letsencrypt/live/jellyfin.cloud.mydomain.tld/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/jellyfin.cloud.mydomain.tld/privkey.pem; # managed by Certbot
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/jellyfin.cloud.mydomain.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/cv/js/sender/v1/cast_sender.js https://www.gstatic.com/eureka/clank/95/cast_sender.js https://www.gstatic.com/eureka/clank/96/cast_sender.js https://www.gstatic.com/eureka/clank/97/cast_sender.js 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://$jellyfin:8096;
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 that /web/#!/ works instead of having to to /web/index.html/#!/
location = /web/ {
# Proxy main Jellyfin traffic
proxy_pass http://$jellyfin:8096/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://$jellyfin:8086;
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;
}
}
On the Synology via command line, I'm running the below command to establish the ssh tunnel:
Code:
ssh -NTC -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes -R 127.0.0.1:8096:127.0.0.1:8096 nginx-ssh@jellyfin.cloud.mydomain.tld
I can see that the connection is established on the VPS:
[vpshost]$ sudo netstat -at
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 localhost:8096 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:https 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:http 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN
tcp 0 288 cloud.mydomain.tld:ssh redacted.ip.addr:15100 ESTABLISHED
tcp 0 0 cloud.mydomain.tld:ssh redacted.ip.addr:54494 ESTABLISHED
tcp6 0 0 [::]:https [::]:* LISTEN
tcp6 0 0 [::]:http [::]:* LISTEN
tcp6 0 0 [::]:ssh [::]:* LISTEN
However, when I try to load my jellyfin instance, I am getting a 502 Bad Gateway error from the web server:
https://jellyfin.cloud.mydomain.tld/ forwards to https://jellyfin.cloud.mydomain.tld/web/ ---> returns 502 Bad Gateway nginx/1.20.1
/var/log/nginx/error.log
2023/08/18 14:02:40 [crit] 699#699: connect() to 23.214.95.221:80 failed (13: Permission denied) while requesting certificate status, responder: r3.o.lencr.org, peer: 23.214.95.221:80, certificate: "/etc/letsencrypt/live/jellyfin.cloud.mydomain.tld/fullchain.pem"
2023/08/18 14:02:40 [crit] 699#699: connect() to 23.214.95.212:80 failed (13: Permission denied) while requesting certificate status, responder: r3.o.lencr.org, peer: 23.214.95.212:80, certificate: "/etc/letsencrypt/live/jellyfin.cloud.mydomain.tld/fullchain.pem"
2023/08/18 14:02:40 [crit] 699#699: connect() to [2600:1406:5600:3::17d6:5fdd]:80 failed (13: Permission denied) while requesting certificate status, responder: r3.o.lencr.org, peer: [2600:1406:5600:3::17d6:5fdd]:80, certificate: "/etc/letsencrypt/live/jellyfin.cloud.mydomain.tld/fullchain.pem"
2023/08/18 14:02:40 [crit] 699#699: connect() to [2600:1406:5600:3::17d6:5fd4]:80 failed (13: Permission denied) while requesting certificate status, responder: r3.o.lencr.org, peer: [2600:1406:5600:3::17d6:5fd4]:80, certificate: "/etc/letsencrypt/live/jellyfin.cloud.mydomain.tld/fullchain.pem"
2023/08/18 14:02:41 [crit] 699#699: *1 connect() to 127.0.0.1:8096 failed (13: Permission denied) while connecting to upstream, client: 146.70.174.92, server: jellyfin.cloud.mydomain.tld, request: "GET /web/ HTTP/2.0", upstream: "http://127.0.0.1:8096/web/index.html", host: "jellyfin.cloud.mydomain.tld"
2023/08/18 14:02:41 [crit] 699#699: *1 connect() to 127.0.0.1:8096 failed (13: Permission denied) while connecting to upstream, client: 146.70.174.92, server: jellyfin.cloud.mydomain.tld, request: "GET /favicon.ico HTTP/2.0", upstream: "http://127.0.0.1:8096/favicon.ico", host: "jellyfin.cloud.mydomain.tld", referrer: "https://jellyfin.cloud.mydomain.tld/web/"
Potential problems that I'm considering, but don't know how to troubleshoot:
- There's some issue with how nginx is set up / allowed to communicate with the ssh tunnel that's been set up? But I don't know how to confirm if that's the case or how to resolve it if that is indeed the situation.
- The ssh tunnel command is setting something up incorrectly? I don't fully understand the "-R 127.0.0.1:8096:127.0.0.1:8096" part.
- There's a permission issue on the VPS between the nginx user (built in / is what nginx is running under) and the nginx-ssh user account (that I set up for certificate-based ssh login)
- The fact that Jellyfin is running in a docker container is causing weirdness? I don't have any other hardware (currently) that I could run a full Jellyfin installation on, so I'm hoping there's a way to keep it on the Synlolgy within a docker container. (I could maybe use a full VM on the Synology, but I think that that will eat up a lot more system resources...)
NOTE: I've replaced all instances of my actual domain name with "mydomain.tld"
Thank you reading and for any suggestions or troubleshooting steps! I'd have put this all in the Jellyfin Reddit thread on the subject, but it appears that that's no longer possible. I'm hoping that this setup isn't too weird, since I'd really like to avoid having to forward ports from my home network.