Running nginx
Described for Ubuntu 20.04. For other OS, look up the appropriate binaries / service controls.
Setting up
sudo apt install nginx-full
Loading nginx configurations
Once changes are made, reload the nginx
server to use the new configuration:
systemctl reload nginx
Monitoring logs:
tail -f /var/log/nginx/access.log
Get list of available modules:
nginx -V 2>&1 | tr -- - '\n' | grep module
Example scripts
Access network WebDAV service over HTTPS
Some notes:
- Some applications may allow their database to be hosted via internal WebDAV.
- WebDAV server on another computer can be exposed to the internet using proxy pass, even if over HTTP internally.
server { server_name pyuxiang.com; location / { deny all; return 404; } location /myWebDAV/ { # the URI proxy_pass http://192.168.100.1:8080/myWebDAVdir; # the WebDAV service proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Real-IP $remote_addr; dav_methods PUT DELETE MKCOL COPY MOVE; dav_ext_methods PROPFIND OPTIONS; dav_access user:rw group:rw all:r; } listen 443 ssl; ... # your certificate stuff }
Parking web application under subdirectory has its challenges...
Main problem: If absolute redirects are used, the wrong location will be queried, e.g. https://example.com/app/login
will query https://example.com/auth/
instead of https://example.com/app/auth/
. This problem exists for both HTML links and JS scripts.
This is a good summary of other solutions: https://serverfault.com/a/932636
# Goal: Run the application in 'http://downstream/' under 'http://upstream/app'. # Problem: Absolute path links go to base URL instead of subdirectory, e.g. '/link' goes to 'http://upstream/link', not 'http://upstream/app/link' as desired. # Solutions: # 1. Deploy in same base uri # - Can control how 'http://upstream/' presents application location /app/ { proxy_pass http://upstream/app/; } # 2. Change absolute to relative paths # - Can control how 'http://upstream/' presents application e.g. img src="/assets/image.png" (absolute) img src="./assets/image.png" (relative) # 3. Reverse proxy necessary subdirectories # - Small number of URIs location /app/ { proxy_pass http://upstream/; } location /link/ { proxy_pass http://upstream/link/; } # 4. Host on subdomain instead of subdirectory # - Okay with no subdirectory server { server_name app.upstream; location / { proxy_pass http://downstream/; } } # 5. Rewrite HTML content location /app/ { sub_filter 'src="/' 'src="/app/'; sub_filter 'src="http://downstream/' 'src="http://upstream/app'; sub_filter_once off; sub_filter_types *; # default is only for 'text/html' proxy_pass http://downstream/; } # 6. Rewrite URL based on referrer location / { if ($http_referrer ~* upstream/app) { rewrite ^(?!/app)/(.*)$ /app/$1 last; return 404; } } location /app/ { proxy_pass http://downstream/; }
Note a couple other concepts that are related, but not relevant.
absolute_redirect
: Changes nginx redirect links from absolute to relative, i.e.return 302 /path;
redirects tohttps://example.com/path
whenon
, and/path
whenoff
. This does not affect redirect links that are already relative.
Also observed in some cases, full restart of Nginx server works great compared to just a reload, not sure the difference. Cache?
proxy_redirect /index/ /app/index/;
can help for initial loading, when referrer is not available. Debugging for the longest time because the first argument in the proxy_redirect wasn't matching to any response locations.
Hardening: https://www.techrepublic.com/article/5-tips-for-better-nginx-security-that-any-admin-can-handle/
# nginx.conf server_tokens off
Note the difference between SSL compression and regular HTTP compression, see forum on having gzip on;
. This is a nice article: https://blog.qualys.com/product-tech/2013/08/07/defending-against-the-breach-attack
HSTS preloading: https://hstspreload.org/
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Removing server headers, because security by obscurity is a pretty important first defence:
sudo apt-get install libnginx-mod-http-headers-more-filter # nginx.conf server_tokens off more_clear_headers Server;
To increase maximum file upload limits, add the client_max_body_size 10MB;
directive as well.
Adding configuration
To combat spam from bots with poor net-etiquette, can block if they have specified signatures, e.g. user agent. For example, this botnet (probably maintained by Bytedance) kept spamming requests for the same resources over the course of the day (with bursts of around 20 cycling between IP addresses), which has a user agent string that includes Bytespider;https://zhanzhang.toutiao.com/
. Even worse it traversed through the media manager to deep scrape all media files.
- /etc/nginx/conf.d/badagent
map $http_user_agent $badagent { default 0; ~*Bytespider 1; # case-insensitive matching }
- /etc/nginx/nginx.conf
http { ... include /etc/nginx/conf.d/badagent; }
- /etc/nginx/sites-available/...
server { ... if ($badagent) { return 403; } }
18.139.239.192 - - [24/May/2023:21:22:22 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/" 18.139.239.192 - - [24/May/2023:21:22:22 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/" 18.136.177.105 - - [24/May/2023:21:22:48 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/" 54.169.1.66 - - [24/May/2023:21:23:14 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/" 52.77.62.64 - - [24/May/2023:21:23:39 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/" 52.76.75.12 - - [24/May/2023:21:24:05 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/" 54.254.46.178 - - [24/May/2023:21:24:31 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/" 54.255.110.242 - - [24/May/2023:21:24:57 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/" 52.76.193.24 - - [24/May/2023:21:25:24 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/" 18.138.77.17 - - [24/May/2023:21:25:50 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/" 13.213.89.206 - - [24/May/2023:21:26:16 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/" 13.215.210.57 - - [24/May/2023:21:26:42 +0800] "GET ... HTTP/2.0" 403 548 "-" "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36'Bytespider;https://zhanzhang.toutiao.com/"
Optimizations to consider:
- Consider reading this seemingly nice article on caching: https://www.mnot.net/cache_docs/
Check that the private key and certificate matches, by comparing the public key:
openssl pkey -pubout -in privkey.pem | openssl md5 openssl x509 -pubkey -in fullchain.pem -noout | openssl md5
TCP forwarding
Idea is to get nginx to function as a forward proxy at the L4 layer, e.g. TCP, then redirect upstream to the specific ports for individual applications. This is done by reading the SNI, then forwarding the packets accordingly. Some references: SO1, SO2.
stream { upstream remote { server 192.168.101.3:443; } upstream https_default_backend { server 127.0.0.1:443; } map $ssl_preread_server_name $upstream { remote.pyuxiang.com remote; default https_default_backend; } server { listen 192.168.101.2:443; proxy_pass $upstream; ssl_preread on; #resolver 192.168.101.1; # if DNS resolution is required #proxy_connect_timeout 5s; #proxy_timeout 3s; } }
If this doesn't work, consider also using separate ports.
PROXY Protocol
Nginx also supports the PROXY protocol, which is basically wrapping TCP (or other protocols) with the PROXY specification. Implementation is dead-simple (but figuring it out was not, especially due to conflicting information from SO). See these references for how to use this: using PROXY and HTTP_REALIP module.
- Ignore the mentions of
X-Forwarded-IP
, which is only applicable from an upstream proxy has already set those headers. proxy_bind
may be useful, but likely bypasses the nginx proxy when serving the requests.
Example below for when using nginx as a TCP proxy as well:
stream { server { listen 192.168.1.1:443; # listens on public interface... proxy_pass 127.0.0.1:443; # ...and forwards to loopback proxy_protocol on; # wraps TCP with PROXY } } http { set_real_ip_from 127.0.0.1; # security measure real_ip_header proxy_protocol; # sets '$remote_addr' using '$proxy_protocol_addr' server { listen 127.0.0.1:443 ssl proxy_protocol; # reads PROXY and sets '$proxy_protocol_addr' } }
This article looks good for a quick read.