Nginx Configuration
My Nginx Configuration
pid /home/plant/nginx/nginx.pid;
http { # Access log format: timestamp [ip] - uri log_format custom_log '$time_iso8601 [$remote_addr] - $uri'; access_log /home/plant/nginx/custom_access.log custom_log;
# Extract the subdomain from *.pages.a-labs.space requests # Example: notes.pages.a-labs.space → $pages_subdomain = "notes" map $host $pages_subdomain { ~^(?P<subdomain>[^.]+)\.pages\.a-labs\.space$ $subdomain; default ""; }
# Map each subdomain to its backend port or full URL map $host $port_mapping { librechat.a-labs.space 3080; invoice.a-labs.space 8001; homarr.a-labs.space 7575; dash.a-labs.space 85; aghome.a-labs.space 84; tor.a-labs.space 8080; jellyfin.a-labs.space 8096; jellyseerr.a-labs.space 5055; suggest.a-labs.space 5000; affine.a-labs.space 3010; fileshare.a-labs.space 6824; runnycode.a-labs.space 3333; runnyhook.a-labs.space 3334; forge.a-labs.space 22290; dl.a-labs.space 31480; files.a-labs.space 31481; dns.a-labs.space https://127.0.0.1:444; # DNS over HTTPS pc.a-labs.space http://192.168.1.100:3000; # Local dev machine # Appwrite (disabled) #appwrite.a-labs.space 2347; #functions.a-labs.space 2347; #~^(.*)\.sites\.a-labs\.space 2347; }
# If $port_mapping is already a full URL, use it as-is; otherwise wrap it as http://127.0.0.1:$port map $port_mapping $backend_url { "~*^http" $port_mapping; default http://127.0.0.1:$port_mapping; }
# Subdomains accessible to the public internet (no IP restriction) map $host $is_domain_public { invoice.a-labs.space 1; dns.a-labs.space 1; affine.a-labs.space 1; fileshare.a-labs.space 1; runnyhook.a-labs.space 1; files.a-labs.space 1; ~^.+\.pages\.a-labs\.space$ 1; # All *.pages.a-labs.space subdomains #appwrite.a-labs.space 1; #functions.a-labs.space 1; default 0; }
# Subdomains excluded from public access even if matched by a public wildcard above map $host $is_public_excluded { notes.pages.a-labs.space 1; default 0; }
# Subdomains that require HTTP basic auth — value becomes the auth realm name map $host $is_require_auth { #pc.a-labs.space "Restricted Area"; default off; }
# IPs allowed to access non-public subdomains (local network + server's own public IP) # The server's public IP is kept up to date automatically — do not remove [AUTO-UPDATE] map $remote_addr $is_trusted_ip { "~*^192\.168\.1\." 1; # Local network "~*^172\." 1; # Docker networks # [AUTO-UPDATE] 88.229.252.78 1; # Server's own public IP default 0; }
sendfile on; tcp_nopush on; types_hash_max_size 4096; types_hash_bucket_size 128;
include /etc/nginx/mime.types; default_type application/octet-stream;
gzip on; gzip_comp_level 9; # 1 (fastest) to 9 (best compression) — 6 is the sweet spot gzip_min_length 1024; # don't bother compressing tiny files gzip_proxied any; # also compress responses for proxied requests gzip_vary on; gzip_types text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml font/woff font/woff2;
client_max_body_size 2048M;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_certificate /etc/letsencrypt/live/a-labs.space/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/a-labs.space/privkey.pem;
ssl_session_cache shared:SSL:10m; ssl_session_timeout 1h; ssl_session_tickets on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'; ssl_prefer_server_ciphers on;
# HTTP — rejects all traffic; HTTPS is required server { listen 80;
server_name a-labs.space *.a-labs.space;
error_page 403 /403.html;
location / { return 403; }
location = /403.html { access_log off; alias /run/media/plant/SAMSUNG/SERVER/public/403.html; } }
# HTTPS — *.pages.a-labs.space — serves a static folder per subdomain server { listen 443 ssl; http2 on;
server_name *.pages.a-labs.space;
root /home/plant/pages/$pages_subdomain;
error_page 404 /404.html;
set $allowed 0;
location / { if ($is_domain_public = 1) { set $allowed 1; }
if ($is_public_excluded = 1) { set $allowed 0; }
if ($is_trusted_ip = 1) { set $allowed 1; }
if ($allowed = 0) { access_log /home/plant/nginx/custom_access.log custom_log; return 403; }
auth_basic $is_require_auth; auth_basic_user_file /etc/nginx/.htpasswd;
try_files $uri $uri/ =404;
# add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; # add_header Pragma "no-cache"; # add_header Expires "0"; }
location = /404.html { if ($is_trusted_ip = 1) { access_log off; }
internal; } }
# HTTPS — *.a-labs.space — reverse proxy to backend services server { listen 443 ssl; http2 on;
server_name *.a-labs.space;
add_header X-XSS-Protection "0"; # Intentionally disabled — header is obsolete and counterproductive add_header X-Content-Type-Options "nosniff";
set $allowed 0;
location / { if ($is_domain_public = 1) { set $allowed 1; }
if ($is_public_excluded = 1) { set $allowed 0; }
if ($is_trusted_ip = 1) { set $allowed 1; }
if ($allowed = 0) { access_log /home/plant/nginx/custom_access.log custom_log; return 403; }
auth_basic $is_require_auth; auth_basic_user_file /etc/nginx/.htpasswd;
# This is important for websockets proxy_http_version 1.1; proxy_redirect off;
access_log off; proxy_pass $backend_url; proxy_pass_request_headers on; 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-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection; proxy_set_header X-Requested-With $http_x_requested_with; proxy_set_header X-Content-Type-Options $http_x_content_type_options; } }
# HTTPS — a-labs.space — serves the main public static website server { listen 443 ssl; http2 on;
server_name a-labs.space;
root /run/media/plant/SAMSUNG/SERVER/public;
error_page 404 /404.html;
location / { if ($is_trusted_ip = 1) { access_log off; }
try_files $uri $uri/ =404;
# add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; # add_header Pragma "no-cache"; # add_header Expires "0"; }
location = /404.html { if ($is_trusted_ip = 1) { access_log off; }
internal; }
# Directory listing for /static/, served from the files folder location /static/ { if ($is_trusted_ip = 1) { access_log off; }
autoindex on; alias /run/media/plant/SAMSUNG/SERVER/public/files/;
add_header X-Content-Type-Options nosniff; add_after_body /custom-file-listing.html; # Injects custom styles into the directory listing page }
# Serves the CSS used by the directory listing above location /custom.css { alias /run/media/plant/SAMSUNG/SERVER/public/custom-file-listing.html; } }}Create Password Authentication for Nginx
1. Create .htpasswd file and first user
sudo htpasswd -c /etc/nginx/.htpasswd username- Replace
usernamewith desired login name - You will be prompted to enter a password
-ccreates the file (use only for first user)
Note
To have htpasswd you need to install httpd-tools, apache2-utils, or apache depending on your distro
2. Add additional users (no -c)
sudo htpasswd /etc/nginx/.htpasswd anotheruser3. Verify file contents
cat /etc/nginx/.htpasswdExpected format:
username:$apr1$randomhash...anotheruser:$apr1$randomhash...