Static File Split Serving
When Nginx handles static files directly and only forwards dynamic requests to Tomcat, you eliminate wasteful thread usage in Tomcat and dramatically improve overall throughput. This chapter covers extension-based routing, directory-based routing, and practical file path management.
Why Split Static File Serving?
When Tomcat handles static files:
- /static/logo.png request → consumes 1 Tomcat thread (Java I/O)
- 100 concurrent static file requests → 100 threads consumed → dynamic requests delayed
When Nginx handles static files:
- /static/logo.png request → Nginx event loop (OS sendfile)
- Tomcat threads used only for business logic
| Item | Nginx Direct Serving | Tomcat Serving |
|---|---|---|
| Throughput | Tens of thousands/sec | Hundreds to thousands/sec |
| Memory | Event-based, minimal | Thread memory required |
| CPU | OS sendfile utilization | JVM overhead |
| Cache-Control | Easy to configure | Requires code/config |
Method 1: Extension-Based Routing
upstream tomcat_backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 443 ssl;
server_name example.com;
root /var/www/myapp;
# Image files — Nginx direct serving
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|bmp|tiff)$ {
expires 30d;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
access_log off;
}
location ~* \.(css|js|mjs)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
location ~* \.(woff|woff2|ttf|eot|otf)$ {
expires 1y;
add_header Cache-Control "public";
add_header Access-Control-Allow-Origin "*"; # Font CORS
access_log off;
}
# Remaining requests — proxy to Tomcat
location / {
proxy_pass http://tomcat_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
}
}
Method 2: Directory-Based Routing
For Spring Boot or traditional Java EE apps where static files live at specific paths like /static/, /resources/, /public/.
server {
listen 443 ssl;
server_name example.com;
root /opt/tomcat/latest/webapps/ROOT;
# /static/ path — Nginx direct serving
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
# 404 if file not found (don't pass to Tomcat)
try_files $uri =404;
}
# /uploads/ — user-uploaded files
location /uploads/ {
alias /var/www/uploads/;
expires 7d;
add_header Cache-Control "public";
# Security: block executable files
location ~* \.(php|jsp|py|rb|sh|exe)$ {
deny all;
}
}
location = /favicon.ico {
log_not_found off;
access_log off;
expires 1y;
}
location = /robots.txt {
log_not_found off;
access_log off;
}
# Remaining — Tomcat
location / {
proxy_pass http://127.0.0.1:8080;
include /etc/nginx/snippets/proxy-params.conf;
}
}
Method 3: External Path Serving (Outside webapps/)
Managing static files separately from Tomcat's webapps/ directory in production.
server {
listen 443 ssl;
server_name example.com;
location /assets/ {
alias /var/www/static/;
expires 1y;
add_header Cache-Control "public, immutable";
gzip_static on; # Serve .gz files if available
}
location /api/ {
proxy_pass http://127.0.0.1:8080/api/;
include /etc/nginx/snippets/proxy-params.conf;
}
location / {
proxy_pass http://127.0.0.1:8080;
include /etc/nginx/snippets/proxy-params.conf;
}
}
gzip Compression and Static File Optimization
# Global gzip (nginx.conf http block)
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml
font/woff
font/woff2;
# Pre-compressed file serving
location ~* \.(css|js)$ {
gzip_static on; # Serve /app.js.gz if available
expires 1y;
add_header Cache-Control "public, immutable";
}
Deployment Script Example
# Extract static files from WAR
VERSION=$(date +%Y-%m-%d-%H%M%S)
mkdir -p /var/www/static/$VERSION
unzip -j myapp.war "static/*" -d /var/www/static/$VERSION/
# Point symlink to current version
ln -sfn /var/www/static/$VERSION /var/www/static/current
# Nginx serves via symlink
# location /static/ {
# alias /var/www/static/current/;
# }
Production Checklist
# 1. Verify static files served by Nginx
curl -I https://example.com/static/css/app.css
# Server: nginx (NOT tomcat)
# 2. Verify cache headers
# Cache-Control: public, immutable
# Expires: (1 year from now)
# 3. Verify no static file requests in Tomcat access log
grep "\.css\|\.js\|\.png" /opt/tomcat/latest/logs/localhost_access_log*.txt
# (should return no results)
Summary
| Routing Method | When to Use |
|---|---|
| Extension-based (`~* .(jpg | css |
Directory-based (location /static/) | Static file paths are clearly separated |
External path (alias) | Static files managed outside WAR |
| gzip_static | Build-time compression + Nginx serving |
| Cache strategy | CSS/JS: immutable 1y, images: 30d, HTML: no-cache |