From ef61e746e7f4eb50f044163c7ae5c7d45f2ae5d9 Mon Sep 17 00:00:00 2001 From: Joby Elliott Date: Tue, 22 Oct 2024 20:17:00 -0600 Subject: [PATCH] vastly expanded nginx config --- add-site.sh | 10 ++- install/nginx-conf.sh | 29 +++++++ install/nginx-conf/00-cloudflare.conf | 26 ++++++ .../nginx-conf/00-rate-limiting-zones.conf | 3 + install/nginx-conf/10-ssl-config.conf | 6 ++ install/nginx-conf/20-security-headers.conf | 8 ++ install/nginx-conf/40-gzip.conf | 6 ++ install/nginx-conf/40-php-config.conf | 10 +++ site-config.conf | 82 ++++++++++--------- 9 files changed, 139 insertions(+), 41 deletions(-) create mode 100644 install/nginx-conf.sh create mode 100644 install/nginx-conf/00-cloudflare.conf create mode 100644 install/nginx-conf/00-rate-limiting-zones.conf create mode 100644 install/nginx-conf/10-ssl-config.conf create mode 100644 install/nginx-conf/20-security-headers.conf create mode 100644 install/nginx-conf/40-gzip.conf create mode 100644 install/nginx-conf/40-php-config.conf diff --git a/add-site.sh b/add-site.sh index e9d4eb1..89b8b70 100755 --- a/add-site.sh +++ b/add-site.sh @@ -39,7 +39,7 @@ main_web_root="/var/www/$domain" sudo mkdir -p "$main_web_root"/{_main/www,subdomains,logs} # Create the user with the web root as home directory and add to www-data and websftpusers groups -sudo useradd -m -d /var/www/$domain -s /usr/sbin/nologin -U -G www-data,websftpusers $username +sudo useradd -m -d /var/www/$domain -s /bin/false -U -G www-data,websftpusers $username echo "$username:$password" | sudo chpasswd # Set ownership and permissions for the main site directory @@ -47,6 +47,11 @@ sudo chown -R "$username:www-data" "$main_web_root" sudo find "$main_web_root" -type d -exec chmod 2750 {} + sudo find "$main_web_root" -type f -exec chmod 640 {} + +# Set ownership and permissions for the main web root +# SFTP chroot requires the user's home directory to be owned by root and not writable by others +sudo chown "root:www-data" "$main_web_root" +sudo chmod 755 "$main_web_root" + # Set ownership and permissions for the logs directory sudo chown root:www-data "$main_web_root/logs" sudo chmod 755 "$main_web_root/logs" @@ -104,9 +109,6 @@ sudo cp "$(realpath "site-config.conf")" "$nginx_config" # replace $DOMAIN placeholder in the nginx config file sudo sed -i "s/\$DOMAIN/$domain/g" "$nginx_config" -# replace $MAIN_WEB_ROOT placeholder in the nginx config file -sudo sed -i "s#\$MAIN_WEB_ROOT#$main_web_root#g" "$nginx_config" - # Enable the site sudo ln -sf "$nginx_config" /etc/nginx/sites-enabled/ diff --git a/install/nginx-conf.sh b/install/nginx-conf.sh new file mode 100644 index 0000000..9715d01 --- /dev/null +++ b/install/nginx-conf.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Check if script is run as root +if [ "$EUID" -ne 0 ]; then + echo "Please run as root or with sudo" + exit 1 +fi + +# Define source and destination directories +SRC_DIR="nginx-conf" +DEST_DIR="/etc/nginx/conf.d" + +# Check if source directory exists +if [ ! -d "$SRC_DIR" ]; then + echo "Source directory '$SRC_DIR' not found" + exit 1 +fi + +# Create destination directory if it doesn't exist +mkdir -p "$DEST_DIR" + +# Copy all .conf files +echo "Copying configuration files..." +cp -v "$SRC_DIR"/*.conf "$DEST_DIR/" + +# Set proper permissions +echo "Setting permissions..." +chown root:root "$DEST_DIR"/*.conf +chmod 644 "$DEST_DIR"/*.conf diff --git a/install/nginx-conf/00-cloudflare.conf b/install/nginx-conf/00-cloudflare.conf new file mode 100644 index 0000000..5a47cdb --- /dev/null +++ b/install/nginx-conf/00-cloudflare.conf @@ -0,0 +1,26 @@ +# Cloudflare IP Ranges +set_real_ip_from 173.245.48.0/20; +set_real_ip_from 103.21.244.0/22; +set_real_ip_from 103.22.200.0/22; +set_real_ip_from 103.31.4.0/22; +set_real_ip_from 141.101.64.0/18; +set_real_ip_from 108.162.192.0/18; +set_real_ip_from 190.93.240.0/20; +set_real_ip_from 188.114.96.0/20; +set_real_ip_from 197.234.240.0/22; +set_real_ip_from 198.41.128.0/17; +set_real_ip_from 162.158.0.0/15; +set_real_ip_from 104.16.0.0/13; +set_real_ip_from 104.24.0.0/14; +set_real_ip_from 172.64.0.0/13; +set_real_ip_from 131.0.72.0/22; +set_real_ip_from 2400:cb00::/32; +set_real_ip_from 2606:4700::/32; +set_real_ip_from 2803:f800::/32; +set_real_ip_from 2405:b500::/32; +set_real_ip_from 2405:8100::/32; +set_real_ip_from 2a06:98c0::/29; +set_real_ip_from 2c0f:f248::/32; + +# Pull real IP from Cloudflare +real_ip_header CF-Connecting-IP; \ No newline at end of file diff --git a/install/nginx-conf/00-rate-limiting-zones.conf b/install/nginx-conf/00-rate-limiting-zones.conf new file mode 100644 index 0000000..ecc467e --- /dev/null +++ b/install/nginx-conf/00-rate-limiting-zones.conf @@ -0,0 +1,3 @@ +# Basic rate limiting zones +limit_req_zone $binary_remote_addr zone=general:10m rate=50r/s; +limit_req_zone $binary_remote_addr zone=php:10m rate=10r/s; \ No newline at end of file diff --git a/install/nginx-conf/10-ssl-config.conf b/install/nginx-conf/10-ssl-config.conf new file mode 100644 index 0000000..8cbe7ec --- /dev/null +++ b/install/nginx-conf/10-ssl-config.conf @@ -0,0 +1,6 @@ +# SSL configuration +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers on; +ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; +ssl_session_cache shared:SSL:10m; +ssl_session_timeout 24h; diff --git a/install/nginx-conf/20-security-headers.conf b/install/nginx-conf/20-security-headers.conf new file mode 100644 index 0000000..d844335 --- /dev/null +++ b/install/nginx-conf/20-security-headers.conf @@ -0,0 +1,8 @@ +# Security headers +add_header Strict-Transport-Security "max-age=31536000" always; +add_header X-Content-Type-Options nosniff always; +add_header X-Frame-Options SAMEORIGIN always; +add_header X-XSS-Protection "1; mode=block" always; +add_header Referrer-Policy "strict-origin-when-cross-origin" always; +add_header Permissions-Policy "geolocation=(),microphone=(),camera=(),payment=(),usb=(),battery=(),display-capture=()" always; +server_tokens off; \ No newline at end of file diff --git a/install/nginx-conf/40-gzip.conf b/install/nginx-conf/40-gzip.conf new file mode 100644 index 0000000..0b2abcb --- /dev/null +++ b/install/nginx-conf/40-gzip.conf @@ -0,0 +1,6 @@ +# Turn on gzip compression +gzip on; +gzip_vary on; +gzip_proxied any; +gzip_comp_level 6; +gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; \ No newline at end of file diff --git a/install/nginx-conf/40-php-config.conf b/install/nginx-conf/40-php-config.conf new file mode 100644 index 0000000..e50dd6f --- /dev/null +++ b/install/nginx-conf/40-php-config.conf @@ -0,0 +1,10 @@ +# Basic PHP configuration +location ~ \.php$ { + limit_req zone=php burst=20 nodelay; + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:/var/run/php/php-fpm.sock; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; +} \ No newline at end of file diff --git a/site-config.conf b/site-config.conf index f0fb88d..c1a16ef 100644 --- a/site-config.conf +++ b/site-config.conf @@ -1,3 +1,4 @@ +# HTTP redirect server { listen 80; listen [::]:80; @@ -5,75 +6,82 @@ server { return 301 https://$host$request_uri; } +# HTTPS server server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name .$DOMAIN; - # SSL Configuration + # SSL certificates ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers on; - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; - # Determine the subdomain and set the root accordingly - # subdomains are the default, so that we get 404s for nonexistant subdomains + # Apply general rate limit + limit_req zone=general burst=100 nodelay; + + # Content Security Policy (needs to be per-domain) + add_header Content-Security-Policy " + default-src 'self' *.$DOMAIN; + script-src 'self' 'unsafe-inline' 'unsafe-eval' *.$DOMAIN; + style-src 'self' 'unsafe-inline' *.$DOMAIN; + img-src 'self' data: *.$DOMAIN; + font-src 'self' data: *.$DOMAIN; + connect-src 'self' *.$DOMAIN; + frame-src 'self' *.$DOMAIN; + media-src 'self' *.$DOMAIN; + object-src 'none'; + base-uri 'self'; + form-action 'self' *.$DOMAIN; + " always; + + # Subdomain handling set $subdomain ''; - set $full_root $MAIN_WEB_ROOT/_main/www; - set $access_log_path $MAIN_WEB_ROOT/logs/$subdomain.access.log; - set $error_log_path $MAIN_WEB_ROOT/logs/$subdomain.error.log; + set $full_root "/var/www/$DOMAIN/_main/www"; + set $access_log_path "/var/www/$DOMAIN/logs/main.access.log"; + set $error_log_path "/var/www/$DOMAIN/logs/main.error.log"; if ($host ~* ^([^.]+)\.$DOMAIN$) { set $subdomain $1; - set $full_root $MAIN_WEB_ROOT/subdomains/$subdomain/www; - set $access_log_path $MAIN_WEB_ROOT/logs/_main.access.log; - set $error_log_path $MAIN_WEB_ROOT/logs/_main.error.log; + set $full_root "/var/www/$DOMAIN/subdomains/$subdomain/www"; + set $access_log_path "/var/www/$DOMAIN/logs/$subdomain.access.log"; + set $error_log_path "/var/www/$DOMAIN/logs/$subdomain.error.log"; } root $full_root; - # Index file names + # Basic settings index index.html index.htm index.php; + client_max_body_size 20M; - # Try files first, then use the router.php file if it exists + # Block .ht* files + location ~ /\.ht { + deny all; + } + + # Main location block location / { try_files $uri $uri/ @router; } - # Use the router.php file for all nonexistant file requests if it exists + # Router handling location @router { if (!-f $document_root/router.php) { return 404; } + limit_req zone=php burst=20 nodelay; fastcgi_pass unix:/var/run/php/php-fpm.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root/router.php; } - # PHP Configuration - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass unix:/var/run/php/php-fpm.sock; - fastcgi_index index.php; - include fastcgi_params; + # Static file handling + location ~* ^.+\.((?!php).)*$ { + expires 30d; + add_header Cache-Control "public, no-transform"; + try_files $uri $uri/ =404; } - # Deny access to .ht* files - location ~ /\.ht { - deny all; - } - - # Additional config options - gzip on; - gzip_vary on; - gzip_proxied any; - gzip_comp_level 6; - gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; - client_max_body_size 20M; - - # Log to both default location and custom site directory, named by subdomain + # Logging access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; access_log $access_log_path; error_log $error_log_path; -} \ No newline at end of file +}