Install

“nginx” package has some variations according to the bundled modules. Choose either one from nginx-light, nginx-core, nginx-full, nginx-extras. In my case, the lightest nginx-light is enough.

sudo apt install nginx-light ssl-cert

Open HTTP port for Nginx.

sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --reload

CrowdSec collection for nginx

Install CrowdSec collections for nginx to block malicious activities.

sudo cscli collections install crowdsecurity/nginx
sudo cscli collections install crowdsecurity/http-cve
sudo systemctl reload crowdsec

Gzip

Gzip compression is turned on by default, but only for text/html. Enabling compression for all other text contents will increase performance.

  • If your server provides HTML files with user input data and sensitive information, enabling gzip compression may expose your server to BREACH attacks. For more details, see the gzip module explanation.
    (In most cases, this shouldn’t happen with the modern web applications.)

Global configuration

If BREACH attacks are not a concern for your environment, turn on gzip globally. Uncomment all the gzip configurations in /etc/nginx/nginx.conf.

##
# Gzip Settings
##

gzip on;

gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

Reload nginx to enable.

sudo systemctl reload nginx

Per-site configuration

You can turn on Gzip within the “server” section. Snippets will help control per-server configurations.

Make /etc/nginx/snippets/gzip.conf file with the same configurations as in the global config file.

gzip on;

gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

Include this snippet in the server section.

server {
        listen ...(snip)

        include snippets/gzip.conf;
        (snip)
}

Site configuration

nginx stores website configuration files in /etc/nginx/sites-available. Add a symlink to those config files in /etc/nginx/sites-enabled to enable a site. (The same as Apache2.)

For more details about each configuration line, please refer to the official manual for ngx_http_core_module.

The simplest example

The simplest example:

  • The domain name is example.jp
  • An HTTP site (non-SSL/TLS)
  • Serves static files from /var/www/html
  • Listen on both IPv4 and IPv6
  • Saves logs to /var/log/nginx/example-jp-*
server {
        listen 80;
        listen [::]:80;

        server_name example.jp;

        root /var/www/html;

        index index.html;

        location / {
                try_files $uri $uri/ =404;
        }

        access_log /var/log/nginx/example.jp-access.log;
        error_log /var/log/nginx/example.jp-error.log;
}

To enable this site, make a symlink at /etc/nginx/sites-enabled/example.jp and reload nginx.

sudo ln -s /etc/nginx/sites-available/example.jp /etc/nginx/sites-enabled/example.jp
sudo systemctl reload nginx

Enable PHP

  • Add index.php as an index file
  • PHP-FPM listens on a Unix socket at /run/php/php-fpm.sock
    (It should be set up when installing php-fpm package.)

Prerequisites

  • PHP and PHP-FPM have to be installed
server {
        listen 80;
        listen [::]:80;

        server_name example.jp;

        root /var/www/html;

        # Add index.php if required
        index index.html index.php;

        location / {
                try_files $uri $uri/ =404;
        }

        access_log /var/log/nginx/example.jp-access.log;
        error_log /var/log/nginx/example.jp-error.log;

        # Pass PHP scripts to FastCGI server
        location ~ \.php {
                # Include PHP snippet
                include snippets/fastcgi-php.conf;

                # Specify php-fpm socket
                fastcgi_pass unix:/run/php/php-fpm.sock;
        }
}

HTTPS server (with HTTP/2)

Open HTTPS port for Nginx.

sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload

Mozilla provides an “SSL Configuration Generator” to generate recommended server configurations for SSL/TLS.
This site provides the complete configuration, but let’s start from the simplest. (You need a proper certificate to complete.)

Use “snakeoil” testing certificate for SSL/TLS

server {
        # Change the port from 80 to 443
        listen 443 ssl;
        listen [::]:443 ssl;

        # Enable http2
        http2 on;

        server_name example.jp;

        # Include snakeoil certificate snippet
        include snippets/snakeoil.conf;

        root /var/www/html;

        index index.html index.php;

        location / {
                try_files $uri $uri/ =404;
        }

        access_log /var/log/nginx/example.jp-access.log;
        error_log /var/log/nginx/example.jp-error.log;

        location ~ \.php {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/run/php/php-fpm.sock;
        }
}

Using the proper certificate is explained in the “Let’s Encrypt part. The “snakeoil” certificate is only for testing and will cause browser warnings.

Enable HTTP/3 (QUIC)

Open HTTP/3 port for Nginx.

sudo firewall-cmd --add-service=http3 --permanent
sudo firewall-cmd --reload

Enable and tell browsers to use HTTP/3 (QUIC) if supported.

server {
        listen 443 ssl;
        listen [::]:443 ssl;
        http2 on;

        # Add HTTP/3 (QUIC) listener
        listen 443 quic reuseport;
        listen [::]:443 quic reuseport;

        # Enable http3
        http3 on;

        # Tell browsers to use HTTP/3 (QUIC) if supported
        add_header Alt-Svc 'h3=":443"; ma=86400';

        server_name example.jp;

        # Include snakeoil certificate snippet
        include snippets/snakeoil.conf;

        root /var/www/html;

        index index.html index.php;

        location / {
                try_files $uri $uri/ =404;
        }

        access_log /var/log/nginx/example.jp-access.log;
        error_log /var/log/nginx/example.jp-error.log;

        location ~ \.php {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/run/php/php-fpm.sock;
        }
}
  • After configuring default server, delete reuseport from listen directive. reuseport must be specified only once throughout the nginx configuration.

Check if the server provides HTTP/3 as expected with HTTP/3 Check.

snippet for HTTPS

HTTP/2 and HTTP/3 configurations have some lines and they are the same for all sites. Integrating these lines into a snippet will make the configuration simpler.

Create /etc/nginx/snippets/https-common.conf with the following content.

# HTTP/2
listen 443 ssl;
listen [::]:443 ssl;
http2 on;

# HTTP/3 (QUIC)
listen 443 quic;
listen [::]:443 quic;
http3 on;

# Tell browsers to use HTTP/3 (QUIC) if supported
add_header Alt-Svc 'h3=":443"; ma=86400';

# HSTS (HTTP Strict Transport Security) for 2 years
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;

With this snippet, the server configuration will be like this.

server {
        include snippets/https-common.conf;

        server_name example.jp;

        # Include snakeoil certificate snippet
        include snippets/snakeoil.conf;

        root /var/www/html;

        index index.html index.php;

        location / {
                try_files $uri $uri/ =404;
        }

        access_log /var/log/nginx/example.jp-access.log;
        error_log /var/log/nginx/example.jp-error.log;

        location ~ \.php {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/run/php/php-fpm.sock;
        }
}

Redirect HTTP to HTTPS

  • Redirect all access to http://example.jp/ to https://example.jp/
# Redirect all HTTP (port 80) access to HTTPS (port 443)
server {
        listen 80;
        listen [::]:80;
        server_name example.jp;

        # 308 = Permanent Redirect
        return 308 https://$host$request_uri;
}

server {
        include snippets/https-common.conf;

        server_name example.jp;

        include snippets/snakeoil.conf;

        root /var/www/html;

        index index.html index.php;

        location / {
                try_files $uri $uri/ =404;
        }

        access_log /var/log/nginx/example.jp-access.log;
        error_log /var/log/nginx/example.jp-error.log;

        location ~ \.php {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/run/php/php-fpm.sock;
        }
}

Default site

The default server configuration is used when no other server configuration matches the request. (Mainly the direct IP access from malicious bots.)
Reject this kind of access by returning 444 (No Response). 444 is a non-standard status code used by nginx, and nginx immediately closes the connection without sending any response to the client.

Create /etc/nginx/sites-available/catch-all with the following content.

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        listen 443 ssl default_server;
        listen [::]:443 ssl default_server;
        http2 on;

        listen 443 quic reuseport default_server;
        listen [::]:443 quic reuseport default_server;
        http3 on;
        add_header Alt-Svc 'h3=":443"; ma=86400';

        include snippets/snakeoil.conf;

        ssl_protocols TLSv1.2 TLSv1.3;

        server_name _;

        # Disconnect
        return 444;
}
  • default_server indicates this server block is the default for the specified port.
    Without this, nginx will use “the first server block” as the default.
  • server_name _; means access anything that doesn’t match other server names. (In short, catch-all.)

Delete reuseport if there is a site already using it. reuseport must be specified only once throughout the nginx configuration.

Disable default site and enable catch-all site.

sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/catch-all /etc/nginx/sites-enabled/catch-all
sudo systemctl reload nginx