I use docker for all of my applications running on my unRAID server, but for nginx I didn't find any image that fulfilled all my needs. So I wrote my own dockerimage which you can find here: https://github.com/Starbix/dockerimages/tree/master/nginx.
The following is my config and should explain what most things do and why.
nginx.conf
This limits the maximal connections per IP
worker_processes auto;
pid /nginx/run/nginx.pid;
daemon off;
pcre_jit on;
events {
worker_connections 2048;
use epoll;
}
http {
limit_conn_zone $binary_remote_addr zone=limit_per_ip:10m;
limit_conn limit_per_ip 128;
limit_req_zone $binary_remote_addr zone=allips:10m rate=150r/s;
limit_req zone=allips burst=150 nodelay;
The custom log format is needed for nginx amplify (statistics and more)
include /nginx/conf/mime.types;
default_type application/octet-stream;
log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'"$host" sn="$server_name" '
'rt=$request_time '
'ua="$upstream_addr" us="$upstream_status" '
'ut="$upstream_response_time" ul="$upstream_response_length" '
'cs=$upstream_cache_status' ;
access_log /nginx/logs/nginx_access.log main_ext;
error_log /nginx/logs/nginx_error.log warn;
The maximum upload size is 25GB
nginx doesn't send that the webserver is nginx
but server
client_max_body_size 25G;
aio threads;
aio_write on;
sendfile on;
keepalive_timeout 15;
keepalive_disable msie6;
keepalive_requests 100;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
more_set_headers 'Server: secret';
Content will be encoded in brötli (which Safari now supports)
gzip off;
brotli on;
brotli_static on;
brotli_buffers 16 8k;
brotli_comp_level 6;
brotli_types
text/css
text/javascript
text/xml
text/plain
text/x-component
application/javascript
application/x-javascript
application/json
application/xml
application/rss+xml
application/vnd.ms-fontobject
font/truetype
font/opentype
image/svg+xml;
This will include all config files from /sites-enabled. The following configuration is the part you'll configure yourself.
include /sites-enabled/*.conf;
include /nginx/custom_sites/*.conf;
include /nginx/conf.d/stub_status.conf;
}
The preceding configuration is part of the included nginx.conf of my Dockerimage.
/sites-enabled/default.conf
These TLS parameters currently result in a 100% score in all categories on https://www.ssllabs.com/ssltest
upstream php-handler {
server unix:/php/run/php-fpm.sock;
}
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers [TLS13+AESGCM+AES256|TLS13+CHACHA20]:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_ecdh_curve secp521r1:secp384r1;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 15m;
ssl_session_tickets off;
ssl_stapling on;
ssl_dyn_rec_enable on;
resolver 1.1.1.1 1.0.0.1 ipv6=off;
ssl_stapling_verify on;
#RSA certificates
ssl_certificate /certs/example.com/fullchain.pem;
ssl_certificate_key /certs/example.com/key.pem;
#ECDSA certificates
ssl_certificate /certs/example.com_ecc/fullchain.pem;
ssl_certificate_key /certs/example.com_ecc/key.pem;
ssl_trusted_certificate /certs/example.com/fullchain.pem;
I use custom HTTP error pages and I used this for creating them: https://github.com/AndiDittrich/HttpErrorPages
error_page 400 401 402 403 404 500 501 502 503 520 521 533 /error/HTTP$status.html;
This server redirects all of the unencrypted connections to https. It uses the same url, except when it's accessed over the IP address, then it redirects to the root of the domain.
server {
listen 8000 default_server;
server_name _;
include /sites-enabled/headers.conf;
if ($host ~ "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b" ) {
return 301 https://example.com$request_uri;
}
return 301 https://$host$request_uri;
}
This server redirects unused subdomains.
server {
listen 4430 ssl http2;
server_name www.example.com;
return 301 https://example.com$request_uri;
include /nginx/conf.d/hsts.conf;
include /sites-enabled/headers.conf;
}
This is the server that listens on the root of the domain, in my case it redirects to the media
subdomain.
server {
listen 4430 ssl http2 default_server;
#listen [::]:4430 ssl http2;
server_name example.com;
include /nginx/conf.d/hsts.conf;
include /sites-enabled/headers.conf;
return 301 https://media.example.com$request_uri;
location /error/ {
alias /www/errorpages/;
internal;
}
}
I use Organizr to organize (duh) all of my apps I regularly use. Definitely check it out if you don't already use it, it's very actively developed and has so many features.
This server also has all of the reverse proxies for my apps I open to the public.
server {
listen 4430 ssl http2;
#listen [::]:4430 ssl http2;
server_name media.example.com;
include /nginx/conf.d/hsts.conf;
include /sites-enabled/headers.conf;
root /www/media;
index index.php index.html;
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location /error/ {
alias /www/errorpages/;
internal;
}
location / {
try_files $uri $uri/ /index.html /index.php?$args =404;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/php/run/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
##
# Virtual Host Configs
##
location /sonarr {
proxy_pass http://192.168.1.126:8989;
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;
proxy_set_header X-Forwarded-Ssl on;
}
location /request {
proxy_pass http://192.168.1.126:3579;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /plex {
proxy_pass http://192.168.1.126:32400/web;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Ssl on;
}
if ($http_referer ~* /plex/) {
rewrite ^/web/(.*) /plex/web/$1? redirect;
}
location /web {
proxy_pass http://192.168.1.126:32400;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Ssl on;
}
location /plexpy {
proxy_pass http://192.168.1.126:8181;
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;
proxy_set_header X-Forwarded-Ssl on;
}
location /radarr {
proxy_pass http://192.168.1.126:7878;
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;
proxy_set_header X-Forwarded-Ssl on;
}
}
I use a subdomain for Plex and you should be able to close the Plex port in your router if you use this if you add the subdomain to the Custom server access URLs
in the Server settings of Plex. You need to use a environmental variable to set it if you use Docker like I do.
upstream plex-upstream {
server 192.168.1.126:32400;
}
upstream nextcloud-upstream {
server 192.168.1.126:8888;
}
server {
listen 4430 ssl http2;
server_name plex.example.com;
include /nginx/conf.d/hsts.conf;
include /sites-enabled/headers.conf;
location /error/ {
alias /www/errorpages/;
internal;
}
location / {
# If a request to / comes in, 301 redirect to the main plex page,
# but only if it doesn't contain the X-Plex-Device-Name header or query argument.
# This fixes a bug where you get permission issues when accessing the web dashboard.
set $test "";
if ($http_x_plex_device_name = '') {
set $test A;
}
if ($arg_X-Plex-Device-Name = '') {
set $test "${test}B";
}
if ($test = AB) {
rewrite ^/$ https://$host/web/index.html;
}
proxy_redirect off;
proxy_buffering off;
# Spoof the request as coming from ourselves since otherwise Plex will block access, e.g. logging:
# "Request came in with unrecognized domain / IP 'tv.example.com' in header Referer; treating as non-local"
proxy_set_header Host $server_addr;
proxy_set_header Referer $server_addr;
proxy_set_header Origin $server_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier;
proxy_set_header Cookie $http_cookie;
## Required for Websockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 36000s; # Timeout after 10 hours
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_pass http://plex-upstream;
}
}
This is Nextcloud, my personal cloud
server {
listen 4430 ssl http2;
server_name cloud.example.com;
include /nginx/conf.d/hsts.conf;
include /sites-enabled/headers.conf;
location /error/ {
alias /www/errorpages/;
internal;
}
client_max_body_size 25G;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header Front-End-Https on;
proxy_pass http://nextcloud-upstream;
proxy_set_header X-Forwarded-Ssl on;
}
}
server {
listen 4430 ssl http2;
#listen [::]:4430 ssl http2;
server_name office.example.com;
include /nginx/conf.d/hsts.conf;
include /sites-enabled/headers.conf;
proxy_buffering off;
location / {
return 301 https://cloud.example.com;
}
location /error/ {
alias /www/errorpages/;
internal;
}
# static files
location ^~ /loleaflet {
proxy_pass https://192.168.1.126:9980;
proxy_set_header Host $http_host;
}
# WOPI discovery URL
location ^~ /hosting/discovery {
proxy_pass https://192.168.1.126:9980;
proxy_set_header Host $http_host;
}
# Main websocket
location ~ /lool/(.*)/ws$ {
proxy_pass https://192.168.1.126:9980;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $http_host;
proxy_read_timeout 36000s;
}
# Admin Console websocket
location ^~ /lool/adminws {
proxy_pass https://192.168.1.126:9980;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $http_host;
proxy_read_timeout 36000s;
}
# download, presentation and image upload
location ^~ /lool {
proxy_pass https://192.168.1.126:9980;
proxy_set_header Host $http_host;
}
}
My blog which you are reading right now is Grav
server {
listen 4430 ssl http2;
#listen [::]:4430 ssl http2;
server_name blog.example.com;
include /nginx/conf.d/hsts.conf;
include /sites-enabled/headers.conf;
root /www/blog;
index index.html index.php;
location /error/ {
alias /www/errorpages/;
internal;
}
location / {
try_files $uri $uri/ /index.php?_url=$uri&$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/php/run/php-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
include fastcgi.conf;
#sfastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
fastcgi_max_temp_file_size 0;
fastcgi_buffer_size 4K;
fastcgi_buffers 64 4k;
}
## Begin - Security
# deny all direct access for these folders
location ~* /(.git|cache|bin|logs|backup|tests)/.*$ { return 403; }
# deny running scripts inside core system folders
location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny running scripts inside user folder
location ~* /user/.*\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny access to specific files in the root folder
location ~ /(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess) { return 403; }
## End - Security
}
I store my passwords on Bitwarden
server {
listen 4430 ssl http2;
#listen [::]:4430 ssl http2;
server_name bitwarden.laubacher.io;
include /nginx/conf.d/hsts.conf;
include /includes/laubacher.tls.conf;
include /includes/headers.conf;
add_header X-Robots-Tag none;
client_max_body_size 128M;
location /error/ {
alias /www/errorpages/;
internal;
}
location / {
proxy_pass http://192.168.1.126:8343;
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;
}
location /notifications/hub {
proxy_pass http://192.168.1.126:3012;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /notifications/hub/negotiate {
proxy_pass http://192.168.1.126:8343;
}
}
/includes/headers.conf
add_header X-Frame-Options "ALLOW-FROM https://*.example.com" always;
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin";
add_header Expect-CT "enforce; max-age=86400; report-uri=https://laubacher.report-uri.io/r/default/ct/enforce";
add_header Expect-Staple "max-age=31536000; report-uri=https://laubacher.report-uri.io/r/default/staple/reportOnly; includeSubDomains; preload";
add_header Content-Security-Policy "frame-ancestors https://*.example.com https://example.com";
add_header X-Robots-Tag none;
Here the blocked countries get a 444
response.
# COUNTRY GEO BLOCK
if ($allowed_country = no) {
return 444;
}
If you want to know more about blocking certain IPs with GeoIP2, check out this guide.
/sites-enabled/geoip.conf
geoip2 /includes/geolite2/GeoLite2-Country.mmdb {
auto_reload 1d;
$geoip2_data_country_code country iso_code;
$geoip2_data_continent_name continent names en;
}
# GEO IP BLOCK SITE 1
map $geoip2_data_country_code $allowed_country {
default yes;
CN no; #China
RU no; #Russia
HK no; #Hong Kong
IN no; #India
IR no; #Iran
VN no; #Vietnam
TR no; #Turkey
EG no; #Egypt
MX no; #Mexico
JP no; #Japan
KP no; #North Korea 🙂
PE no; #Peru
BR no; #Brazil
UA no; #Ukraine
ID no; #Indonesia
TH no; #Thailand
AE no;
AF no;
SA no;
MY no;
BY no;
MD no;
AL no;
}