Setting up NGINX on Ubuntu Server

Introduction

I am considering two kinds of platform (system) setup. The package provided NGINX (being RHEL or Debian systems) or NGINX from scratch. T

To make this guide valid for both situations, NGINX as package versus from source/scratch, I am using variables,

For RHEL and Debian provided NGINX,

confdir=/etc/nginx/conf.d
logdir=/var/log/nginx

For NGINX from scratch,

#confdir=/usr/local/nginx/conf.d
#logdir=logs

Installation

On Debian systems,

    apt install nginx
systemctl status nginx
    netstat -antupe --inet --inet6 | grep LISTEN | grep 80

    cp -pi /etc/nginx/sites-available/default /etc/nginx/sites-available/default.dist
    cp -pi /etc/nginx/nginx.conf /etc/nginx/nginx.conf.dist
    ls -lhF /var/www/html/index.nginx-debian.html
    rm -f /var/www/html/index.nginx-debian.html
echo "<p>nothing here" > /var/www/html/index.html

On RHEL systems, make sure the EPEL repo is available and proceed,

    yum install nginx
    netstat -antupe --inet --inet6 | grep LISTEN | grep 80
systemctl start nginx
systemctl enable nginx

    cp -pi /etc/nginx/nginx.conf /etc/nginx/nginx.conf.dist
ls -alhF /usr/share/nginx/html/

Configuration

It is a good practice to split virtual host configurations into files so those are easily maintainable. I am even taking the server {} stanza out of the main nginx.conf to have those only in the conf.d/ folder. The special catch-all-not-a-virtual-host-per-say will live there too.

The overall http stanza

Look for users,

grep www-data /etc/passwd
grep www-data /etc/group

Make sure there is no server {} stanza in the global configuration, that process owner is defined and allowing NGINX to scale/downscale by itself,

vi nginx.conf

user www-data;
worker_processes  auto;

Enabling compression and defining a custom log format to display the compression ratio,

gzip  on;
log_format compression '$remote_addr - $remote_user [$time_local] '
    '"$request" $status $body_bytes_sent '
    '"$http_referer" "$http_user_agent" "$gzip_ratio"';

And make sure the conf.d/ folder is enabled, and disable the Debian shit,

include /etc/nginx/conf.d/*.conf;
#include /etc/nginx/sites-enabled/*;

The catch-all-not-a-virtual-host-per-say

Note the server_name is _. Redirecting both http and https requests to e.g. https://doc.nethence.com/ no matter what, so there will be no unwanted URLs nor URLs spreading around (including heretic URLs containing IP addresses). People will be forced to use and will know about the right URL as for the default HTTP service on this host.

#cd /usr/local/nginx/conf.d/
cd /etc/nginx/conf.d/
vi CATCHALL.conf 

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

        server_name _;
        access_log logs/CATCHALL.access.log compression;
        error_log  logs/CATCHALL.error.log warn;

        return 301 https://doc.nethence.com/;
}

server {
        listen 443 ssl;
        listen [::]:443 ssl;
        ssl_certificate /etc/letsencrypt/live/doc.nethence.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/doc.nethence.com/privkey.pem;
        ssl_prefer_server_ciphers on;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS;
        ssl_session_cache shared:SSL:20m;
        ssl_session_timeout 10m;
        add_header Strict-Transport-Security "max-age=31536000";

        server_name _;
        access_log logs/CATCHALL.access.ssl.log compression;
        error_log  logs/CATCHALL.error.ssl.log warn;

        return 301 https://doc.nethence.com/;
}

The main virtual host

This virtual host has the advantage of using a fully valid SSL certificate by Let’s Encrypt (right FQDN).

The location > root parameter here is pointing to the html folder, meaning either /var/www/html/ (as package) or /usr/local/nginx/html/ (from scratch).

#cd /usr/local/nginx/conf.d/
cd /etc/nginx/conf.d/
vi doc.nethence.com.conf

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

        server_name doc.nethence.com;
        access_log logs/doc.nethence.com.access.log compression;
        error_log  logs/doc.nethence.com.error.log warn;

        return 301 https://$host$request_uri;
}

server {
        listen 443 ssl;
        listen [::]:443 ssl;
        ssl_certificate /etc/letsencrypt/live/doc.nethence.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/doc.nethence.com/privkey.pem;
        ssl_prefer_server_ciphers on;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:DH+3DES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS;
        ssl_session_cache shared:SSL:20m;
        ssl_session_timeout 10m;
        add_header Strict-Transport-Security "max-age=31536000";

        server_name doc.nethence.com doc;
        access_log logs/doc.nethence.com.access.ssl.log compression;
        error_log  logs/doc.nethence.com.error.ssl.log warn;

        autoindex on;
        location / {
                root   html;
                index  index.html index.htm;
                try_files $uri $uri/ =404;
        }
}

Note. Redirecting HTTP to HTTPS.

The other virtual hosts

The location > root here is will be /data/www/VIRTUAL_HOST/.

Creating virtual hosts (escaping the $host $request_uri $server_name NGINX variables while keeping our own defined shell variables for use with cat),

#confdir=/usr/local/nginx/conf.d
confdir=/etc/nginx/conf.d
#logdir=logs
logdir=/var/log/nginx

cd $confdir/

for vhost in {qweqwe,asdasd,qweasd}.doc.nethence.com; do

cat > $vhost.conf <<-EOF
server {
        listen 80;
        listen [::]:80;

        server_name $vhost ${vhost%%\.*};
        access_log $logdir/$vhost.access.log compression;
        error_log  $logdir/$vhost.error.log warn;

        return 301 https://\$host\$request_uri;
}

server {
        listen 443 ssl;
        listen [::]:443 ssl;
        ssl_certificate /etc/letsencrypt/live/doc.nethence.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/doc.nethence.com/privkey.pem;
        ssl_prefer_server_ciphers on;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:DH+3DES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS;
        ssl_session_cache shared:SSL:20m;
        ssl_session_timeout 10m;
        add_header Strict-Transport-Security "max-age=31536000";

        server_name $vhost ${vhost%%\.*};
        access_log $logdir/$vhost.access.ssl.log compression;
        error_log  $logdir/$vhost.error.ssl.log warn;
            root   /data/www/\$server_name/;
        autoindex on;
}
EOF
done; unset vhost

ls -lhF {qweqwe,asdasd,qweasd}.doc.nethence.com.conf
grep server_name {qweqwe,asdasd,qweasd}.doc.nethence.com.conf

Note: for internal networks, the site can be reached without the domain part, this is the purpose of the additional ${vhost%%\.*} alias for $server_name.

Creating some content for those virtual hosts to serve,

mkdir -p /data/www/qweqwe.doc.nethence.com/
mkdir -p /data/www/asdasd.doc.nethence.com/
mkdir -p /data/www/qweasd.doc.nethence.com/

echo '<p>qweqwe' > /data/www/qweqwe.doc.nethence.com/index.html
echo '<p>asdasd' > /data/www/asdasd.doc.nethence.com/index.html
echo '<p>qweasd' > /data/www/qweasd.doc.nethence.com/index.html

apply with systemctl restart nginx or /usr/local/nginx/sbin/nginx -s reload and check,

curl -v https://asdasd.doc.nethence.com/

Note. Eventually overcome the SSL hostname mismatch adding -k to the curl command.

Note. You cannot use $server_name for access_log and error_log unless it’s fine with you to change the log folder perms accordingly (www-data needs to write in it).

Note. Redirecting HTTP to HTTPS.

Reverse Proxy

To setup an http reverse proxy, simply change location in the server stanza,

    location / {
        proxy_pass http://APPLICATION_ADDRESS:PORT;
    }

apply with systemctl restart nginx or /usr/local/nginx/sbin/nginx -s reload.

Be careful, some applications need to know about the vhost and port that are called. So you shall play with those settings. Here is some specific conf for GitLab,

    proxy_set_header Host $http_host;

here’s a working specific conf for Gollum, assuming you’re running SSL only on the nginx side,

            proxy_pass http://127.0.0.1:4567;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Proto https;

also jenkins conf.

some other possible configs,

    #proxy_set_header Host $host; #instead of http_host
            #proxy_set_header X-Real-IP $remote_addr;
    #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    #proxy_redirect off;

and here is another one,

        proxy_set_header X-Real-IP $remote_addr;

robots.txt and favicon.ico

vi conf.d/drop.include

# https://www.nginx.com/resources/wiki/start/topics/recipes/dokuwiki/
location = /robots.txt  { access_log off; log_not_found off; }
location = /favicon.ico { access_log off; log_not_found off; }
location ~ /apple-touch-icon { access_log off; log_not_found off; }
location ~ /\.          { access_log off; log_not_found off; deny all; }
location ~ ~$           { access_log off; log_not_found off; deny all; }

then onto the vhost’s server stanza,

include conf.d/drop.include;

Authentication

in the server or http stanza,

            location ^~ /private/ {
                    auth_basic "Restricted Area";
                    auth_basic_user_file htpasswd;
            }

then create or edit a password file,

cd /usr/local/nginx/conf/
#apt install apache2-utils
#yum install httpd-?
htpasswd -c htpasswd NEW_USER
#DO NOT chmod 600 htpasswd as the www-data user needs to read it

if files exists already,

htpasswd htpasswd EXISTING_USER

apply with systemctl restart nginx or /usr/local/nginx/sbin/nginx -s reload.

CGI

Install and run the FastCGI helper,

apt -y install fcgiwrap
systemctl list-unit-files | grep fcgi
systemctl status fcgiwrap.socket
ls -lhF /var/run/fcgiwrap.socket
systemctl status fcgiwrap.service

Make sure NGINX is ready for that,

ls -lhF $confdir/../fastcgi_params
ls -lhF $confdir/../fastcgi.conf

Make sure your script is executable and test it,

vhost=host.example.com

mkdir -p /data/www/$vhost/

cat > /data/www/$vhost/index.cgi <<-EOF9
#!/bin/ksh

cat <<EOF
Content-type: text/html

<p>hello \`host \$REMOTE_ADDR | awk '{print \$NF}'\` (\$REMOTE_ADDR)
EOF
EOF9

chmod +x /data/www/$vhost/index.cgi
ls -lhF /bin/ksh
export REMOTE_ADDR=::1
/data/www/$vhost/index.cgi
unset REMOTE_ADDR

and setup those parms into the vhost server stanza e.g.,

vi $confdir/$vhost.conf

root /data/www/$server_name;
    index index.cgi maintenance.html;

location ~ (\.cgi|\.py|\.sh|\.pl|\.lua)$ {
    gzip off;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
    include fastcgi_params;
    fastcgi_param DOCUMENT_ROOT /data/www/$server_name;
    fastcgi_param SCRIPT_FILENAME /data/www/$server_name$fastcgi_script_name;
}

Note. Be careful with $server_name in the fastcgi parameters: if you do not use a vhost the server name becomes _.

Note. include fastcgi_params points to conf/fastcgi_params already

apply with systemctl restart nginx or /usr/local/nginx/sbin/nginx -s reload.

Redirect Regex

Permanent redirect, getting rid of .html extention from the URL,

    rewrite ^(/.+)\.html$ $1 permanent;

Permanent redirect, renaming folder /service/ to /server/,

    rewrite ^/service/(.+) /server/$1 permanent;

Cache Control

into the http stanza and before the Virtual Host Configs server stanzas,

vi nginx.conf

# Expires map
map $sent_http_content_type $expires {
    default                    off;
    text/html                  epoch;
    text/css                   max;
    application/javascript     max;
    ~image/                    max;
}

refs.

Conclusion

Clean up your UNIX™ env variables when you have finished,

unset confdir logdir

Troubleshooting

If you get a 301 Moved Permanently instaed of the actual content,

curl $vhost

Refs.

References