Nginx Static File Serving
Serving static files efficiently using:
- Nginx.
- File compression using gzip, brotli.
- Browser caching using
Last-Modifiedresponse,If-Modified-Sinceconditional requests.
Files are not dynamically compressed at run time as this will increase latency and CPU utilization. Instead, files need to be precompressed (as part of CI/CD pipeline or during deployment) and the compressed versions will be served based on browser support as indicated in the Accept-Encoding request header.
Eg: index.html, index.html.br and index.html.gz are available and when the browser requests index.html, nginx will automatically server the index.html.br or index.html.gz based on browser support. This is highly efficient and usually consumes less than ~1% of CPU.
Step 1: Nginx Custom Docker Container
The official nginx docker container does not support brotli, a custom container needs to be built based on alpine.
File: Dockerfile
# Create a custom docker container including the nginx brotli module
FROM alpine:3.19
WORKDIR /root
RUN apk add --no-cache nginx nginx-mod-http-brotli
EXPOSE 80/tcp
CMD ["nginx", "-g", "daemon off;"]File: docker-compose.yaml
# Docker compose to server static files with nginx with brotli/gzip
# Filename: docker-compose.yaml
version: "2.4"
name: static-example-com
services:
static:
container_name: static.example.com
build: .
restart: always
volumes:
- ./www:/usr/share/nginx/html:ro
- ./conf/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./conf/nginx.conf:/etc/nginx/nginx.conf:ro
labels:
- com.centurylinklabs.watchtower.enable=true
- traefik.enable=true
- traefik.http.routers.static.rule=Host(`static.example.com`)
- traefik.http.routers.static.tls=true
- traefik.http.routers.static.tls.certresolver=lets-encryptStep 2: nginx Config
File: nginx.conf
# Default nginx.conf from nginx official docker container
# Adding brotli support
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
# Add brotli static, gzip static is default
load_module modules/ngx_http_brotli_static_module.so;
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;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}File: default.conf
# Nginx virtual host config fragment
# Filename: /etc/nginx/conf.d/default.conf
server {
listen 80;
# Enable brotli/gzip support
brotli_static on;
gzip_static on;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
autoindex on;
# Permit the browser to cache files for 1 hour
# Browser must issue a conditional GET request with the If-Modified-Since header
# to avoid using stale content
add_header Cache-Control "max-age=3600, public, no-transform, must-revalidate, proxy-revalidate";
# add_header Cache-Control "max-age=3600, public, no-transform";
}
}Step 3: Prepare Files
Generate compressed files using brotli and gzip and keep the originals.
# Use max compression for files, keep (don't delete) original uncompressed file
brotli -9vk *.html *.js *.json *.css *.csv *.txt
gzip -9vk *.html *.js *.json *.css *.csv *.txtStep 4: Verification
Use browser dev tools to ensure the compressed files are automatically loaded instead of original versions.
Notes
- The
Accept-Encodingrequest header will includegzip, deflate, brto list the browser supported compression options. - The
Content-Encodingresponse header must showbrto indicate that compressed content is transferred on the wire. - When both
brandgzencoding are supported,bris preferred overgz.