Yet another nginx reverse proxy config

Tiago Marques4 years ago

This is, to the best of my research, a reasonably good config with some security and performance improvements, hope it does help others struggling to configure a nginx reverse proxy for traccar.

In my case I had problems with websockets and in the end the problem was that I was setting "large_client_header_buffers" to 2 2k which is too small.

This config is for a 1 core 1Gb cloud server so if you have a better host you can increase "worker_connections", "worker_processes" and "worker_rlimit_nofile".

/etc/nginx/nginx.conf

user  nginx;
worker_processes  auto;
# worker_rlimit_nofile  = worker_connections * worker_processes
worker_rlimit_nofile 1024;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    # ssl
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions
    ssl_session_tickets off;

    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers off;

    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /etc/pki/tls/certs/ca-bundle.crt;

    resolver 1.1.1.1 valid=300s;
    resolver_timeout 5s;

    tcp_nodelay     on;
    sendfile        on;
    tcp_nopush      on;

    keepalive_timeout  65;

    # timeouts
    client_body_timeout   12;
    client_header_timeout 12;
    send_timeout          10;

    # buffers
    client_body_buffer_size       16K;
    client_header_buffer_size     1k;
    large_client_header_buffers   4 4k;
    client_max_body_size          8m;

    # Enable gzip but do not remove ETag headers
    gzip on;
    gzip_vary on;
    gzip_comp_level 4;
    gzip_min_length 256;
    gzip_disable "msie6";
    gzip_http_version 1.1;
    gzip_buffers 16 8k;
    gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
    gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

    include /etc/nginx/conf.d/*.conf;
}

cat /etc/nginx/conf.d/traccar.conf

upstream traccar {
        server 127.0.0.1:8082;
    }
server {
    listen 443 ssl http2;
    server_name track.fqdn.com;

    ssl_certificate /etc/letsencrypt/live/track.fqdn.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/track.fqdn.com/privkey.pem; # managed by Certbot


    add_header Cache-Control no-cache;
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";

    access_log /var/log/nginx/traccar_access.log combined;
    error_log /var/log/nginx/traccar_error.log warn;

    location /.well-known {
        alias /usr/share/nginx/html;
    }

    location / {
        proxy_pass http://traccar/;

        proxy_http_version      1.1;
        proxy_set_header        Upgrade             $http_upgrade;
        proxy_set_header        Connection          "upgrade";

        proxy_set_header        Host                $host;
        proxy_set_header        X-Forwarded-Host    $host;
        proxy_set_header        X-Forwarded-Server  $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        Proxy               "";

        proxy_pass_request_headers  on;

    }
    # deny blank or invalid ("-") host headers
    if ($http_user_agent = "") {
        return 403;
    }
    if ($http_user_agent = "-") {
        return 403;
    }
    if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
        return 403;
    }
    if ($http_user_agent ~* msnbot|scrapbot) {
        return 403;
    }
    if ($request_method !~ ^(GET|HEAD|POST)$ ) {
        return 444;
    }
}
server {
    listen 80;
    server_name track.fqdn.com;

    # deny blank or invalid ("-") host headers
    if ($http_user_agent = "") {
        return 403;
    }
    if ($http_user_agent = "-") {
        return 403;
    }
    if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
        return 403;
    }
    if ($http_user_agent ~* msnbot|scrapbot) {
        return 403;
    }
    if ($request_method !~ ^(GET|HEAD|POST)$ ) {
        return 444;
    }

    if ($host = track.fqdn.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot
}
Tiago Marques4 years ago

So I had to edit some things on my config, and came back here to share the updates.

Changes:
You also need the "PUT" HTTP method or you won't be able to edit any config stuff;
No need to prevent bogus user agents in http as https (on https will suffice, as http already redirects);
Webserver should not disclose its version (add "server_tokens off;" to nginx.conf).

cat /etc/nginx/nginx.conf

user  nginx;
worker_processes  auto;
# worker_rlimit_nofile  = worker_connections * worker_processes
worker_rlimit_nofile 1024;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    # hide server info
    server_tokens off;

    # ssl
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions
    ssl_session_tickets off;

    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers off;

    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /etc/pki/tls/certs/ca-bundle.crt;

    resolver 1.1.1.1 valid=300s;
    resolver_timeout 5s;

    tcp_nodelay     on;
    sendfile        on;
    tcp_nopush      on;

    keepalive_timeout  65;

    # timeouts
    client_body_timeout   12;
    client_header_timeout 12;
    send_timeout          10;

    # buffers
    client_body_buffer_size       16K;
    client_header_buffer_size     1k;
    large_client_header_buffers   4 4k;
    client_max_body_size          8m;

    # Enable gzip but do not remove ETag headers
    gzip on;
    gzip_vary on;
    gzip_comp_level 4;
    gzip_min_length 256;
    gzip_disable "msie6";
    gzip_http_version 1.1;
    gzip_buffers 16 8k;
    gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
    gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

    include /etc/nginx/conf.d/*.conf;
}

cat /etc/nginx/conf.d/traccar.conf

upstream traccar {
        server 127.0.0.1:8082;
    }
server {
    listen 443 ssl http2;
    server_name track.fqdn.com;

    ssl_certificate /etc/letsencrypt/live/track.fqdn.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/track.fqdn.com/privkey.pem; # managed by Certbot


    add_header Cache-Control no-cache;
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";

    access_log /var/log/nginx/traccar_access.log combined;
    error_log /var/log/nginx/traccar_error.log warn;

    location /.well-known {
        alias /usr/share/nginx/html;
    }

    location / {
        proxy_pass http://traccar/;

        proxy_http_version      1.1;
        proxy_set_header        Upgrade             $http_upgrade;
        proxy_set_header        Connection          "upgrade";

        proxy_set_header        Host                $host;
        proxy_set_header        X-Forwarded-Host    $host;
        proxy_set_header        X-Forwarded-Server  $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        Proxy               "";

        proxy_pass_request_headers  on;

    }
    # deny blank or invalid ("-") host headers
    if ($http_user_agent = "") {
        return 403;
    }
    if ($http_user_agent = "-") {
        return 403;
    }
    if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
        return 403;
    }
    if ($http_user_agent ~* msnbot|scrapbot) {
        return 403;
    }
    if ($request_method !~ ^(GET|HEAD|POST|PUT)$ ) {
        return 444;
    }
}
server {
    listen 80;
    server_name track.fqdn.com;

    if ($host = track.fqdn.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot
}
Tiago Marques4 years ago

EDIT2: DELETE http method is also needed or logout will not not work.

...
    if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE)$ ) {
        return 444;
    }
...