Installing and Configuring nginx on Ubuntu 22.04

Installing and Configuring nginx on Ubuntu 22.04

Overview

On Ubuntu 22.04, it is desirable to install the latest nginx directly from the nginx repository, rather than from the main Ubuntu repository. This gives access to better modules, more current nginx, etc.

Installation

Adding the Repository

First, we need to add the nginx repository:

curl -s https://nginx.org/keys/nginx_signing.key | sudo apt-key add -
sudo sh -c 'echo "deb http://nginx.org/packages/ubuntu/ jammy nginx" >> /etc/apt/sources.list.d/nginx_(nginx.org).list'
sudo apt update
sudo apt install nginx libnginx-mod-http-geoip

This will install nginx and the mod-http-geoip module.

Disabling geoip2 nginx Module

Next, we will disable the newer geoip2 nginx module:

cd /etc/nginx/modules-enabled
sudo rm 50-mod-http-geoip2.conf

Configure geoip Tables

We will need to create the landing location for the geoip databases:

sudo mkdir /usr/share/geoip
touch geoip_update.sh

Next, we will add this script to the geoip_update.sh file:

#!/bin/bash

### Sample download script for https://mailfud.org/geoip-legacy/
### - Adjust DBDIR and FILES below
### - Adjust XTABLES and XT_GEOIP_BUILD if needed
### - Copy script to /etc/cron.weekly or similar for your OS,
###   note that /etc/cron.* filename MUST NOT HAVE .sh extension,
###   rename to /etc/cron.weekly/geoip_update
### Contact: admin@mailfud.org

# Database directory
DBDIR=/usr/share/geoip
# Files to download (.dat.gz suffix not required)
FILES="GeoIP GeoIPCity"
#FILES="GeoIP GeoIPv6 GeoIPCity GeoIPCityv6 GeoIPASNum GeoIPASNumv6 GeoIPOrg GeoIPISP"

# If http proxy needed
#https_proxy="http://foo.bar:3128"

# Uncomment XTABLES to enable iptables xt_geoip updating
# Must contain filename for legacy IPv4/IPv6 CSV file (GeoIP-legacy.csv)
#
# xtables-addons needs to be installed
# (apt-get linux-headers-generic xtables-addons-dkms)
# More info: https://inai.de/projects/xtables-addons/
#
#XTABLES="GeoIP-legacy.csv"
# Standard distribution location for xtables script, change if using custom
#XT_GEOIP_BUILD=/usr/lib/xtables-addons/xt_geoip_build

### v0.24
### - add support for Ubuntu 22.04 /usr/libexec/xtables-addons/xt_geoip_build
### v0.23
### - fix xtables 3.8+, requires dbip-country-lite.csv
### v0.22
### - fix xtables stuff
### v0.21
### - added GeoIPCityv6, GeoIPASNumv6, fix https_proxy export

# DB directory
test -w $DBDIR && cd $DBDIR 2>/dev/null || { echo "Invalid directory: $DBDIR"; exit 1; }

# Sleep 0-600 sec if started from cron
if [ ! -t 0 ]; then sleep $((RANDOM/54)); fi

if [ "$XTABLES" != "" ]; then
	FILES="$FILES $XTABLES"
fi

export https_proxy
for f in $FILES; do
	# Make sure .gz is stripped
	f=${f%*.gz}
	# Make sure .dat exists
	if [[ ! "$f" =~ \.csv ]]; then f=${f%*.dat}.dat; fi
	# .gz files are kept on disk to compare timestamps (-N)
	wget -nv -N -T 30 --max-redirect 0 https://mailfud.org/geoip-legacy/$f.gz
	RET=$?
	if [ $RET -ne 0 ]; then
		echo "wget $f.gz failed: $RET" >&2
		continue
	fi
	# Unpack and replace files atomically
	if gzip -dc $f.gz >$f.tmp; then
		if ! diff $f $f.tmp >/dev/null 2>&1; then
			if [ "$f" = "$XTABLES" ]; then XUPD=1; fi
			echo "updating $f"
			chmod 644 $f.tmp
			/bin/mv -f $f.tmp $f
		else
			echo "$f is up to date"
		fi
	else
		echo "gunzip $f failed" >&2
		rm -f $f.gz
	fi
	rm -f $f.tmp
done

if [ "$XTABLES" != "" ]; then
	if [ -z "$XT_GEOIP_BUILD" ]; then
		if [ -f /usr/lib/xtables-addons/xt_geoip_build ]; then
			XT_GEOIP_BUILD=/usr/lib/xtables-addons/xt_geoip_build
		else
			XT_GEOIP_BUILD=/usr/libexec/xtables-addons/xt_geoip_build
		fi
	fi
	if [ ! -f "$XT_GEOIP_BUILD" ]; then
		echo "xt_geoip_build not found, xtables-addons-common package not installed?" >&2
		exit 0
	fi
	if [ ! -f "GeoIP-legacy.csv" ]; then
		echo "GeoIP-legacy.csv not found, cannot update xt_geoip" >&2
		exit 0
	fi
	if [ ! -z "$XUPD" -o "$(find /usr/share/xt_geoip -name 'US.*' -mtime -14 2>/dev/null)" = "" ]; then
		mkdir -m 755 /usr/share/xt_geoip 2>/dev/null
		# Convert to dbip-country-lite format if needed (xtables-addons 3.8+)
		if grep dbip-country-lite $XT_GEOIP_BUILD >/dev/null; then
			cat $DBDIR/GeoIP-legacy.csv | tr -d '"' | cut -d, -f1,2,5 >$DBDIR/dbip-country-lite.csv.tmp &&
			/bin/mv -f $DBDIR/dbip-country-lite.csv.tmp $DBDIR/dbip-country-lite.csv
			XCMD="perl $XT_GEOIP_BUILD -D /usr/share/xt_geoip -S $DBDIR"
		else
			XCMD="perl $XT_GEOIP_BUILD -D /usr/share/xt_geoip $DBDIR/GeoIP-legacy.csv"
		fi
		RET=$($XCMD 2>/dev/null | tail -1)
		if [[ "$RET" =~ (Zimbabwe|ZW) ]]; then
			echo "xt_geoip updated"
		else
			echo "something went wrong with xt_geoip update" >&2
			echo "do you have perl module Text::CSV_XS / libtext-csv-xs-perl installed?" >&2
			echo "try running command manually:" >&2
			echo "$XCMD" >&2
		fi
	else
		echo "xt_geoip is up to date"
	fi
fi

We can then execute this script to get the latest databases:

chmod +x geoip_update.sh
sudo ./geoip_update.sh

It is a good idea to put this script in a cron and let it run once a week to stay up to date. Otherwise, log in and run it manually periodically.

Configuring nginx.conf File

Now, we need to add the following block to the nginx.conf module. It is best to comment out the existing access and error log file lines, and place this block directly below it:

# New json format
	log_format json_analytics escape=json '{'
        '"msec": "$msec", ' # request unixtime in seconds with a milliseconds resolution
        '"connection": "$connection", ' # connection serial number
        '"connection_requests": "$connection_requests", ' # number of requests made in connection
        '"pid": "$pid", ' # process pid
        '"request_id": "$request_id", ' # the unique request id
        '"request_length": "$request_length", ' # request length (including headers and body)
        '"remote_addr": "$remote_addr", ' # client IP
        '"remote_user": "$remote_user", ' # client HTTP username
        '"remote_port": "$remote_port", ' # client port
        '"time_local": "$time_local", '
        '"time_iso8601": "$time_iso8601", ' # local time in the ISO 8601 standard format
        '"request": "$request", ' # full path no arguments if the request
        '"request_uri": "$request_uri", ' # full path and arguments if the request
        '"args": "$args", ' # args
        '"status": "$status", ' # response status code
        '"body_bytes_sent": "$body_bytes_sent", ' # the number of body bytes exclude headers sent to a client
        '"bytes_sent": "$bytes_sent", ' # the number of bytes sent to a client
        '"http_referer": "$http_referer", ' # HTTP referer
        '"http_user_agent": "$http_user_agent", ' # user agent
        '"http_x_forwarded_for": "$http_x_forwarded_for", ' # http_x_forwarded_for
        '"http_host": "$http_host", ' # the request Host: header
        '"server_name": "$server_name", ' # the name of the vhost serving the request
        '"request_time": "$request_time", ' # request processing time in seconds with msec resolution
        '"upstream": "$upstream_addr", ' # upstream backend server for proxied requests
        '"upstream_connect_time": "$upstream_connect_time", ' # upstream handshake time incl. TLS
        '"upstream_header_time": "$upstream_header_time", ' # time spent receiving upstream headers
        '"upstream_response_time": "$upstream_response_time", ' # time spend receiving upstream body
        '"upstream_response_length": "$upstream_response_length", ' # upstream response length
        '"upstream_cache_status": "$upstream_cache_status", ' # cache HIT/MISS where applicable
        '"ssl_protocol": "$ssl_protocol", ' # TLS protocol
        '"ssl_cipher": "$ssl_cipher", ' # TLS cipher
        '"scheme": "$scheme", ' # http or https
        '"request_method": "$request_method", ' # request method
        '"server_protocol": "$server_protocol", ' # request protocol, like HTTP/1.1 or HTTP/2.0
        '"pipe": "$pipe", ' # "p" if request was pipelined, "." otherwise
        '"gzip_ratio": "$gzip_ratio", '
        '"http_cf_ray": "$http_cf_ray",'
        '"geoip_city_lat": "$geoip_latitude",'
        '"geoip_city_lon": "$geoip_longitude",'
        '"geoip_city": "$geoip_city",'
        '"geoip_region_code": "$geoip_region",'
        '"geoip_country_code": "$geoip_country_code"'
    '}';

	access_log /var/log/nginx/access.log json_analytics;
	error_log /var/log/nginx/error.log;

	geoip_country /usr/share/geoip/GeoIP.dat;
	geoip_city /usr/share/geoip/GeoIPCity.dat;

Now, we can test the configuration and make sure it comes back as valid. If so, enable and restart the service:

nginx -t
sudo systemctl enable nginx
sudo systemctl restart nginx