🌐 Installing NGINX¶

NGINX is a free, open-source, high-performance HTTP server and reverse proxy, as well as an IMAP/POP3 proxy server.
NGINX is known for its high performance, stability, rich feature set, simple configuration, and low resource consumption.
📥 Installation¶
📦 Install NGINX¶
NGINX is one of a handful of servers written to address the C10K problem. Unlike traditional servers, NGINX doesn't rely on threads to handle requests. Instead it uses a much more scalable event-driven (asynchronous) architecture. This architecture uses small, but more importantly, predictable amounts of memory under load. NGINX scales in all directions: from the smallest VPS all the way up to large clusters of servers.
# install NGINX
sudo apt-get install nginx
# check NGINX status
sudo nginx -t && sudo service nginx status
⚙️ Configure NGINX¶
Debian places NGINX configuration files in /etc/nginx and its sub-directories.
Shared configuration is kept directly in that root directory.
Specific server setups reside in the sites-available directory, with symlinks in the sites-enabled directory to make them active.
Define the NGINX html directory
Before proceeding, define the required variable:
DEFAULT_HTML_DIR: the absolute path to the directory where NGINX serves shared static content from (such as the default 404 page).
# define the absolute path to the nginx html directory
DEFAULT_HTML_DIR=/path/to/html # e.g. /var/www/default
📁 Prepare the html directory¶
Create the html directory and grant www-data read access
# abort if DEFAULT_HTML_DIR is unset or empty
: "${DEFAULT_HTML_DIR:?is not set}"
# grant the NGINX worker user (www-data) read-only access to one html
# directory, existing + future content, and traverse-only on its parents
# $1 = html directory served by NGINX (absolute path)
apply_acl_www() {
local dir="$1"
local parent
# guard: refuse relative paths
case "$dir" in /*) ;; *) echo "apply_acl_www: absolute path required: $dir" >&2; return 1;; esac
# existing content (recursive, covers files AND dirs).
# capital X grants execute only where it makes sense:
# dirs -> r-x (enter + list)
# files -> r-- (r-x only if the file is already executable)
sudo setfacl -R -m u:www-data:rX "$dir"
# existing content: drop all "other" permissions — www-data reads through
# its named ACL entry; nobody else (service users, container subuids) can
sudo chmod -R o-rwx "$dir"
# future content: default ACL on every directory (only dirs can carry one).
# Anything created inside later inherits www-data read access at creation;
# new subdirs also inherit the default ACL itself, so the rule propagates.
# o::- is pinned explicitly: without it the default ACL snapshots the
# directory's current "other" bits and new files come out world-readable.
sudo find "$dir" -type d -exec setfacl -d -m u:www-data:rX,o::- {} +
# parents up to (not including) /: traverse-only (x) so www-data can pass
# through the whole chain — no read, no listing.
# / itself is world-traversable everywhere, no ACL needed there.
parent=$(dirname "$dir")
while [ "$parent" != "/" ]; do
sudo setfacl -m u:www-data:x "$parent"
parent=$(dirname "$parent")
done
}
mkdir -p "${DEFAULT_HTML_DIR}"
apply_acl_www "${DEFAULT_HTML_DIR}"
📝 Install the configuration¶
Install /etc/nginx/nginx.conf
# install the main NGINX configuration
sudo tee /etc/nginx/nginx.conf > /dev/null <<'EOF'
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
}
http {
##
# Core Settings
##
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
proxy_headers_hash_max_size 1024;
proxy_headers_hash_bucket_size 128;
client_max_body_size 10M;
proxy_intercept_errors on;
server_tokens off;
##
# MIME types; default fallback
##
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# WebSocket upgrade map
##
map $http_upgrade $connection_upgrade { default upgrade; '' close; }
##
# One catch-all HTTP server: redirect everything to HTTPS
##
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 308 https://$host$request_uri;
}
##
# TLS/SSL Settings
##
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
##
# Security Headers
##
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
##
# Gzip Settings
##
gzip on;
gzip_comp_level 5;
gzip_min_length 1400;
gzip_vary on;
gzip_proxied any;
gzip_types
application/atom+xml
application/javascript
application/json
application/ld+json
application/manifest+json
application/wasm
application/xhtml+xml
application/xml
image/svg+xml
text/cache-manifest
text/css
text/plain
text/vcard;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
EOF
Fix the MIME type for .webmanifest files
# fix mime type for .webmanifest files
sudo tee /etc/nginx/conf.d/webmanifest-mime.conf > /dev/null <<EOF
types {
application/manifest+json webmanifest;
}
EOF
🚫 Install the 404 page¶

The custom default 404 webpage is available here: download.
The error-404.conf snippet only declares a shared, internal location for /404.html:
a virtual host activates it by including the snippet and declaring error_page 404 /404.html; in its server block.
Install the 404.html file
# abort if DEFAULT_HTML_DIR is unset or empty, warn if it is not a directory
: "${DEFAULT_HTML_DIR:?is not set}"
[ -d "${DEFAULT_HTML_DIR}" ] || echo "Error: DEFAULT_HTML_DIR is not a directory" >&2
# download the default 404 webpage
wget https://docs.fum-server.fr/files/404.html -O "${DEFAULT_HTML_DIR}/404.html"
Create the error-404.conf snippet
# abort if DEFAULT_HTML_DIR is unset or empty, warn if it is not a directory
: "${DEFAULT_HTML_DIR:?is not set}"
[ -d "${DEFAULT_HTML_DIR}" ] || echo "Error: DEFAULT_HTML_DIR is not a directory" >&2
# create the shared 404 location snippet (DEFAULT_HTML_DIR expands into the root directive)
sudo tee /etc/nginx/snippets/error-404.conf > /dev/null <<EOF
##
# Common 404 page for all servers
##
location = /404.html {
root ${DEFAULT_HTML_DIR};
internal;
}
EOF
🚧 Install the Maintenance page¶

The custom maintenance webpage is available here: download.
The error-maintenance.conf snippet only declares a shared, internal location for /maintenance.html:
a virtual host activates it by including the snippet and declaring error_page 502 503 504 /maintenance.html; in its server block.
Install the maintenance.html file
# abort if DEFAULT_HTML_DIR is unset or empty, warn if it is not a directory
: "${DEFAULT_HTML_DIR:?is not set}"
[ -d "${DEFAULT_HTML_DIR}" ] || echo "Error: DEFAULT_HTML_DIR is not a directory" >&2
# download the maintenance webpage
wget https://docs.fum-server.fr/files/maintenance.html -O "${DEFAULT_HTML_DIR}/maintenance.html"
Create the error-maintenance.conf snippet
# abort if DEFAULT_HTML_DIR is unset or empty, warn if it is not a directory
: "${DEFAULT_HTML_DIR:?is not set}"
[ -d "${DEFAULT_HTML_DIR}" ] || echo "Error: DEFAULT_HTML_DIR is not a directory" >&2
# create the shared maintenance location snippet (DEFAULT_HTML_DIR expands into the root directive)
sudo tee /etc/nginx/snippets/error-maintenance.conf > /dev/null <<EOF
##
# Common maintenance page for all servers
##
location = /maintenance.html {
root ${DEFAULT_HTML_DIR};
internal;
}
EOF
🔄 Restart NGINX¶
# restart NGINX service
sudo nginx -t && sudo service nginx restart