Skip to content

Nginx Configuration

My Nginx Configuration

a-labs.space.conf
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 username with desired login name
  • You will be prompted to enter a password
  • -c creates 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 anotheruser

3. Verify file contents

cat /etc/nginx/.htpasswd

Expected format:

username:$apr1$randomhash...
anotheruser:$apr1$randomhash...