Skip to content

Instantly share code, notes, and snippets.

@icedterminal
Last active November 8, 2024 00:07
Show Gist options
  • Save icedterminal/c42a0a8c660bafa7c301db5b410b86ab to your computer and use it in GitHub Desktop.
Save icedterminal/c42a0a8c660bafa7c301db5b410b86ab to your computer and use it in GitHub Desktop.
Jellyfin + NGINX
server {
listen 80;
listen 443 ssl;
http2 on;
# HTTP3 ONLY
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
quic_retry on;
quic_gso on;
ssl_early_data on;
http3 on;
http3_hq off;
http3_max_concurrent_streams 128;
http3_stream_buffer_size 64k;
server_name _;
# A reject causes problems with SSL/TLS testing. Don't use.
#ssl_reject_handshake on;
ssl_conf_command Options KTLS; # Use kernel processing to make TLS faster.
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256; # Tell OpenSSL what ciphers we are going to use. TLS1.3 only.
ssl_protocols TLSv1.3; # Modern only.
ssl_prefer_server_ciphers on; # Server will use the best cipher from OpenSSL
ssl_session_tickets off;
ssl_session_timeout 10m;
ssl_ecdh_curve prime256v1;
ssl_buffer_size 1400;
ssl_session_cache shared:ipsecure:10m;
# we use a fake cert so TLS works to send a 444.
ssl_certificate "/etc/nginx/invalid/invalid.pem";
ssl_certificate_key "/etc/nginx/invalid/invalid.key";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin" always;
return 444;
access_log /var/log/nginx/block.log main buffer=32k flush=5m;
}
server {
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
# HTTP3 ONLY
#listen 443 quic reuseport;
#listen [::]:443 quic reuseport;
#quic_retry on;
#quic_gso on;
#ssl_early_data on;
#http3 on;
#http3_hq off;
#http3_max_concurrent_streams 128;
#http3_stream_buffer_size 64k;
server_name jf.example.me;
set $jellyfin 127.0.0.1;
keepalive_timeout 60;
ssl_conf_command Options KTLS; # Use kernel processing to make TLS faster.
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256; # Tell OpenSSL what ciphers we are going to use. TLS1.3 only.
ssl_protocols TLSv1.3; # Modern only.
ssl_prefer_server_ciphers on; # Server will use the best cipher from OpenSSL
ssl_session_tickets off;
ssl_session_timeout 24h;
ssl_ecdh_curve secp256r1;
ssl_buffer_size 4k;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 valid=300s;
ssl_session_cache shared:dotmesecure:10m;
ssl_ocsp_cache shared:dotmestaple:10m;
ssl_certificate "/etc/letsencrypt/live/example.me/fullchain.pem";
ssl_certificate_key "/etc/letsencrypt/live/example.me/privkey.pem";
ssl_trusted_certificate "/etc/letsencrypt/live/example.me/chain.pem";
add_header Content-Security-Policy "base-uri 'none'; connect-src 'self'; default-src 'none'; font-src 'self' data:; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self' blob: data: https:; manifest-src 'self'; media-src 'self' blob:; object-src 'none'; script-src 'self' 'unsafe-inline' blob:; script-src-elem 'self' https://www.gstatic.com/cv/js/sender/v1/cast_sender.js https://www.gstatic.com/eureka/clank/ blob:; style-src 'self' 'unsafe-inline'; worker-src 'self' blob:;";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy strict-origin-when-cross-origin;
#add_header Alt-Svc 'h3=":$server_port"; ma=86400';
add_header X-protocol $server_protocol always;
add_header Cache-Control "private$jf_content";
access_log /var/log/nginx/jellyfin.log main buffer=32k flush=5m;
if ($request_method !~ ^(GET|HEAD|POST|DELETE)$ ) {
return 405;
}
sendfile on;
tcp_nopush on;
location = / {
return 301 https://$host/web/;
}
location / {
proxy_pass http://$jellyfin:8096;
proxy_http_version 1.1;
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;
proxy_redirect http:// https://;
proxy_buffering on;
proxy_buffers 16 4k;
proxy_buffer_size 4k;
proxy_busy_buffers_size 8k;
proxy_temp_file_write_size 8k;
proxy_max_temp_file_size 16k;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
#proxy_ssl_certificate /etc/nginx/jellyfin/cert.pem;
#proxy_ssl_certificate_key /etc/nginx/jellyfin/priv.key;
#proxy_ssl_protocols TLSv1.2 TLSv1.3;
#proxy_ssl_session_reuse on;
}
location = /web/ {
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_pass http://$jellyfin:8096;
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;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location ~ /Items/(.*)/Images {
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;
proxy_cache jellyfin;
proxy_cache_revalidate on;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status; # This is only to check if cache is working
}
location ~\.(pl|cgi|py|sh|lua|asp|php)$ {
return 444;
}
location ~ /\. {
return 444;
}
}
user [www-data|http];
worker_processes auto;
pid [/var/run|/run]/nginx.pid;
worker_rlimit_nofile 2048;
events {
worker_connections 1024;
}
http {
# Hides nginx/version
#server_tokens off;
# Send the data as soon as it’s ready to go.
tcp_nodelay on;
client_body_buffer_size 1m;
client_max_body_size 20m;
client_header_buffer_size 1k;
large_client_header_buffers 4 16k;
# Proxy performance
open_file_cache max=5000 inactive=20s;
open_file_cache_valid 60s;
open_file_cache_min_uses 4;
open_file_cache_errors on;
include /etc/nginx/mime.types;
default_type application/octet-stream; # Undefined mime types are downloaded instead of rendered.
####
# Compress contents in order of preference.
# Brotli is preffered over Gzip.
# Files that match the mime types are compressed.
# 1 is low compression using low CPU.
# 9 is high compression using high CPU.
# Gzip defaults: Nginx uses 1, Apache uses 6.
####
#brotli on;
#brotli_static on;
#brotli_comp_level 7;
#brotli_types application/atom+xml application/javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/x-javascript application/xhtml+xml application/xml font/eot font/opentype font/otf font/truetype font/woff font/woff2 image/svg+xml image/vnd.microsoft.icon image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml;
#gzip on;
#gzip_vary on;
#gzip_min_length 100;
#gzip_comp_level 4;
#gzip_proxied any;
#gzip_types application/atom+xml application/javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/x-javascript application/xhtml+xml application/xml font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml;
####
# Logging
####
log_format main [$time_local] ' $status' ' $request_time' ' $remote_user' ' $remote_addr' ' "$request" $body_bytes_sent' ' "$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log main buffer=32k flush=5m;
error_log /var/log/nginx/error.log; # Add 'info' if you need a more detailed breakdown. 'debug' is excessive and requires debug build.
####
# Set headers to keep contents in client cache.
# This reduces server load and speeds up browsing.
# You need to connect to this per site (server block).
# It's best to avoid caching all of "~applications/" or else various site functions will break.
# This one is specific to Jellyfin.
####
map $sent_http_content_type $jf_content {
# Undefined content is off
"default" "";
# HTML gets unique define
"text/html" ", epoch";
# Text content, 30 days / 1 month
"text/javascript" ", max-age=2592000";
"text/css" ", max-age=2592000";
# Fonts, 365 days / 12 months
"application/vnd.ms-fontobject" ", max-age=31536000";
"application/font-woff" ", max-age=31536000";
"application/x-font-truetype" ", max-age=31536000";
"application/x-font-opentype" ", max-age=31536000";
"~font/" "max-age=31536000";
# Media, 180 days / 6 months
# You don't want to send cache responses for video or audio.
"~image/" ", max-age=15552000";
}
proxy_cache_path /var/cache/nginx/jellyfin levels=1:2 keys_zone=jellyfin:256m max_size=15g inactive=30d use_temp_path=off;
include fastcgi.conf;
include deny.conf; # Drops direct IP access.
#include local.conf; # LAN sites for internal use.
include sites-available/*; # vhosts for TLDs.
}
@Santos-Barla
Copy link

Hello friend, could you reupload your document, since the host went down and I read it when I was at work and it looked very complete, could you reupload it?

@icedterminal
Copy link
Author

@Santos-Barla Apologies, my server was rebooted due to pending updates. It should be up now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment