Table of Contents

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:

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.

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:

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.

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.