Nginx on a MicroVM
Nginx is a lightweight, high-performance web server. It is ideal for MicroVMs thanks to its minimal memory consumption and its ability to handle thousands of simultaneous connections.
Preparing the Image
sudo mount -o loop rootfs.img /mnt/microvm
# Debian/Ubuntu
sudo chroot /mnt/microvm bash -c '
apt update && apt install -y nginx
'
# Alpine
sudo chroot /mnt/microvm bash -c '
apk add nginx
mkdir -p /run/nginx
'
sudo umount /mnt/microvm
Optimal Configuration
sudo mount -o loop rootfs.img /mnt/microvm
sudo tee /mnt/microvm/etc/nginx/nginx.conf << 'NGINXCONF'
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 512;
multi_accept on;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
access_log /var/log/nginx/access.log main;
# Performance
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
# Gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript;
# Buffer
client_body_buffer_size 16K;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# Temp paths
client_temp_path /var/cache/nginx/client_temp;
proxy_temp_path /var/cache/nginx/proxy_temp;
fastcgi_temp_path /var/cache/nginx/fastcgi_temp;
uwsgi_temp_path /var/cache/nginx/uwsgi_temp;
scgi_temp_path /var/cache/nginx/scgi_temp;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
NGINXCONF
# Make sure the cache directory exists
mkdir -p /mnt/microvm/var/cache/nginx
sudo umount /mnt/microvm
Virtual Host
sudo tee /mnt/microvm/etc/nginx/sites-available/default << 'NGINXVHOST'
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
# Static files caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Error pages
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
}
NGINXVHOST
Running the MicroVM
qemu-system-x86_64 \
-M microvm -enable-kvm -cpu host -m 256m -smp 2 \
-kernel vmlinuz \
-append "console=ttyS0 root=/dev/vda rw" \
-drive id=rootfs,file=rootfs.img,format=raw,if=none,cache=none \
-device virtio-blk-device,drive=rootfs \
-netdev user,id=net0,hostfwd=tcp::8080-:80 \
-device virtio-net-device,netdev=net0 \
-nographic -serial stdio
Setting Up the Guest
# Create an index page
cat > /var/www/html/index.html << 'HTML'
<!DOCTYPE html>
<html>
<head><title>Nginx on MicroVM</title></head>
<body>
<h1>Nginx on QEMU MicroVM</h1>
<p>Running successfully!</p>
</body>
</html>
HTML
# Test config
nginx -t
# Start nginx
nginx
# or via the service
service nginx start
Auto Start
sudo tee /mnt/microvm/etc/init.d/nginx << 'NGINXINIT'
#!/sbin/openrc-run
NGINX=/usr/sbin/nginx
PIDFILE=/run/nginx.pid
depend() {
need net
keyword timeout 60
}
start() {
ebegin "Starting Nginx"
mkdir -p /run/nginx /var/cache/nginx
$NGINX -g "pid $PIDFILE;"
eend $?
}
stop() {
ebegin "Stopping Nginx"
start-stop-daemon --stop --pidfile $PIDFILE
eend $?
}
NGINXINIT
sudo chmod +x /mnt/microvm/etc/init.d/nginx
sudo chroot /mnt/microvm rc-update add nginx default
SSL/TLS
# Generate self-signed certificate
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/nginx-selfsigned.key \
-out /etc/ssl/certs/nginx-selfsigned.crt
# SSL server block
cat > /etc/nginx/sites-available/ssl << 'SSLCONF'
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
root /var/www/html;
index index.html;
}
SSLCONF
ln -s /etc/nginx/sites-available/ssl /etc/nginx/sites-enabled/
nginx -t && service nginx restart
Performance Tuning
# Check the worker processes
nginx -T | grep worker_processes
# Reload the config without downtime
nginx -s reload
# Test performance
ab -n 1000 -c 10 http://localhost/
# Check active connections
ss -s
Monitoring
# Check the nginx status
service nginx status
# Check the logs
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log
# Check the stats (requires the stub_status module)
cat > /etc/nginx/conf.d/status.conf << 'STATUS'
server {
listen 8080;
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
}
STATUS
Troubleshooting
Address already in use
# Check port 80
ss -tlnp | grep :80
# Stop the other service or change Nginx's port
listen 8080;
Permission denied
# Fix the permissions
chown -R www-data:www-data /var/www/html
chmod -R 755 /var/www/html
chown -R www-data:www-data /var/cache/nginx
Worker connections not enough
# Increase worker_connections
events {
worker_connections 1024;
}
# Check ulimit
ulimit -n