← Back to DevBytes

Void Linux Performance Tuning for Web Servers

Introduction to Void Linux Performance Tuning for Web Servers

Void Linux is a lightweight, independent distribution built around the xbps package manager and the runit init system. Unlike systemd-based distributions, Void boots fast, runs fewer background services by default, and gives administrators fine-grained control over what executes on the system. These characteristics make it an excellent foundation for high-performance web servers, but out-of-the-box settings are tuned for general-purpose use rather than production workloads. This tutorial walks through the practical steps required to transform a fresh Void Linux installation into a lean, responsive web server capable of handling significant traffic.

Why Performance Tuning Matters

Default kernel parameters, network stack settings, and service configurations are conservative. They prioritize stability across diverse hardware rather than throughput for a specific workload. On a web server, this means you may be leaving substantial performance on the table. Proper tuning improves request latency, increases concurrent connection capacity, reduces memory waste, and makes the system more resilient under load spikes. On Void specifically, the absence of systemd means you also avoid the overhead of socket activation daemons, D-Bus sessions, and other background machinery that consumes CPU cycles and RAM even when idle.

System Preparation and Baseline Measurement

Before changing anything, establish a baseline. You cannot improve what you cannot measure. Install benchmarking and monitoring tools, record current performance, and then compare after each change.

# Update the system
sudo xbps-install -Su

# Install monitoring and benchmarking tools
sudo xbps-install -S htop iotop sysstat iproute2 curl apachebench wrk

# Record baseline network and system stats
sar -n DEV 1 5 > baseline_network.txt
vmstat 1 5 > baseline_vm.txt

Keep these baseline files. After applying tuning changes, generate new reports and compare them. Look for reduced context switches, lower wait CPU percentages, higher network throughput, and reduced memory pressure.

Kernel and Network Stack Tuning

The Linux kernel exposes tunable parameters through /proc/sys. On Void, the standard way to persist these is via /etc/sysctl.d/ configuration files. Create a dedicated file for web server tuning.

Creating the Sysctl Configuration

sudo tee /etc/sysctl.d/99-webserver.conf << 'EOF'
# Increase TCP max buffer size
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 262144
net.core.wmem_default = 262144

# TCP buffer auto-tuning
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# Increase connection queue sizes
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# Enable TCP Fast Open
net.ipv4.tcp_fastopen = 3

# Reduce TIME_WAIT and enable reuse
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_tw_buckets = 1048576

# Keepalive tuning
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5

# Disable ICMP redirects and enable reverse path filtering
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.rp_filter = 1

# Increase local port range for outbound connections
net.ipv4.ip_local_port_range = 10240 65535

# Increase file descriptor limits at kernel level
fs.file-max = 2097152
fs.nr_open = 2097152

# Inotify tuning for applications that watch files
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 512

# Virtual memory and swap behavior
vm.swappiness = 10
vm.vfs_cache_pressure = 50
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
EOF

# Apply immediately
sudo sysctl --system

Understanding Key Parameters

File Descriptor and Process Limits

Beyond the kernel-level fs.file-max, individual processes are constrained by user-level limits configured through PAM. Void uses the standard /etc/security/limits.conf mechanism.

sudo tee /etc/security/limits.d/99-webserver.conf << 'EOF'
# Raise limits for the web server user
nginx    soft  nofile  1048576
nginx    hard  nofile  1048576
nginx    soft  nproc   65535
nginx    hard  nproc   65535

# Apply to all users if you run multiple services
*        soft  nofile  65535
*        hard  nofile  65535
EOF

Verify the limits are applied by logging in as the service user and checking:

# Check soft and hard limits
su - nginx -s /bin/sh -c "ulimit -n"
su - nginx -s /bin/sh -c "ulimit -Hn"

Service Management with Runit

Void uses runit for service supervision. Services live in /etc/sv/ and are enabled by symlinking them into /var/service/. The runit model is simple: each service directory contains a run script that execs the daemon in the foreground. If the process exits, runit restarts it automatically.

Disabling Unnecessary Services

A lean system runs only what is required. Audit enabled services and disable anything unnecessary.

# List enabled services
ls -la /var/service/

# Disable a service (example: remove agetty on serial if unused)
sudo rm /var/service/agetty-ttyS0

# Stop a service without removing the symlink
sudo sv down <servicename>

# Check status of all services
sudo sv status /var/service/*

Common services to review include additional agetty instances, sshd if you use key-only access on a non-standard port, dhcpcd if you use static IPs, and nanoklogd if you do not need kernel log buffering.

Creating a Custom Nginx Service

If you need custom environment variables or resource limits for your web server, create a tailored service directory rather than modifying the default.

sudo mkdir -p /etc/sv/nginx-custom

sudo tee /etc/sv/nginx-custom/run << 'EOF'
#!/bin/sh
# Set resource limits before exec
ulimit -n 1048576
ulimit -u 65535

# Set performance-related environment variables
export LANG=C
export LC_ALL=C

exec chpst -u nginx:nginx nginx -g 'daemon off;'
EOF

sudo chmod +x /etc/sv/nginx-custom/run

# Enable the custom service
sudo ln -s /etc/sv/nginx-custom /var/service/nginx-custom

The chpst command from the runit suite changes the process user and group. The daemon off; directive keeps Nginx in the foreground so runit can supervise it properly.

Installing and Tuning Nginx

Nginx is the most common web server choice on Void for high-traffic deployments. Install it and configure it for performance.

sudo xbps-install -S nginx

# Enable the default service
sudo ln -s /etc/sv/nginx /var/service/nginx

Worker Process and Connection Tuning

Edit the main Nginx configuration to match your hardware. The key is to set worker processes equal to the number of CPU cores and to raise the worker connection limit.

# Determine CPU core count
nproc

sudo tee /etc/nginx/nginx.conf << 'EOF'
user nginx;
worker_processes auto;
worker_rlimit_nofile 1048576;
pid /run/nginx.pid;

events {
    worker_connections 65535;
    use epoll;
    multi_accept on;
}

http {
    # Basic settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    keepalive_requests 1000;
    types_hash_max_size 2048;
    server_tokens off;

    # Buffer sizes
    client_body_buffer_size 16k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;
    client_max_body_size 8m;

    # Connection limiting
    reset_timedout_connection on;
    send_timeout 30;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 256;
    gzip_types
        text/plain
        text/css
        text/xml
        application/json
        application/javascript
        application/xml
        application/xml+rss
        image/svg+xml;

    # Logging - disable or buffer for performance
    access_log off;
    error_log /var/log/nginx/error.log warn;

    # Open file cache
    open_file_cache max=10000 inactive=5m;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}
EOF

# Test and reload
sudo nginx -t
sudo sv reload nginx

Explanation of Key Directives

PHP-FPM Tuning for Dynamic Content

If your web server runs PHP applications, PHP-FPM is the standard process manager. Its default pool configuration is tuned for small workloads. Adjust it based on available RAM and expected traffic.

sudo xbps-install -S php-fpm
sudo ln -s /etc/sv/php-fpm /var/service/php-fpm

Calculate how many PHP worker processes you can run. Divide available RAM (minus what Nginx and the OS need) by the average memory consumption per PHP process. A typical PHP process uses 30 to 80 MB depending on the application.

sudo tee /etc/php/php-fpm.d/www.conf << 'EOF'
[www]
user = nginx
group = nginx
listen = /run/php-fpm/php-fpm.sock
listen.owner = nginx
listen.group = nginx

# Dynamic process management
pm = dynamic
pm.max_children = 120
pm.start_servers = 12
pm.min_spare_servers = 6
pm.max_spare_servers = 24
pm.max_requests = 1000

# Slow log for debugging
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 5s

# Status page (restrict access in Nginx)
pm.status_path = /fpm-status
ping.path = /ping
EOF

sudo sv restart php-fpm

The pm.max_requests setting is important: it recycles worker processes after handling a set number of requests, preventing memory leaks in long-running PHP applications from accumulating indefinitely.

Database Tuning with PostgreSQL

For web servers running a database locally, PostgreSQL is a strong choice on Void. Install and tune it for a dedicated server scenario.

sudo xbps-install -S postgresql
sudo ln -s /etc/sv/postgresql /var/service/postgresql

Edit the main PostgreSQL configuration file. The exact path depends on the version, typically /var/lib/postgresql/data/postgresql.conf after initialization.

# Initialize if not already done
sudo -u postgres initdb -D /var/lib/postgresql/data

sudo tee -a /var/lib/postgresql/data/postgresql.conf << 'EOF'

# Performance tuning
max_connections = 100
shared_buffers = 2GB
effective_cache_size = 6GB
work_mem = 32MB
maintenance_work_mem = 512MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200

# Connection pooling hint
listen_addresses = '127.0.0.1'
EOF

sudo sv restart postgresql

These values assume a server with 8 GB of RAM. Adjust shared_buffers to roughly 25% of total RAM and effective_cache_size to 75% of total RAM. The random_page_cost of 1.1 is appropriate for SSD storage, signaling to the query planner that random reads are nearly as cheap as sequential reads.

Transparent Huge Pages and CPU Governor

Transparent Huge Pages (THP) can cause latency spikes on web servers due to kernel compaction stalls. Disable THP for predictable performance.

# Check current THP status
cat /sys/kernel/mm/transparent_hugepage/enabled

# Disable immediately
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag

# Persist across reboots via rc.local
sudo tee /etc/rc.local << 'EOF'
#!/bin/sh
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
EOF
sudo chmod +x /etc/rc.local

Set the CPU governor to performance mode to prevent frequency scaling latency from affecting request handling.

# Check available governors
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors

# Set performance governor
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# Persist via rc.local (append to existing file)
echo 'for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do echo performance > "$cpu"; done' | sudo tee -a /etc/rc.local

I/O Scheduler Selection

The I/O scheduler affects disk latency and throughput. For SSD and NVMe storage, the none (formerly noop) or mq-deadline scheduler is optimal because these devices have negligible seek time and can handle reordering themselves.

# Check current scheduler
cat /sys/block/sda/queue/scheduler

# Set to none for NVMe/SSD
echo none | sudo tee /sys/block/sda/queue/scheduler

# Persist via rc.local
echo 'for dev in /sys/block/sd*/queue/scheduler /sys/block/nvme*/queue/scheduler; do [ -w "$dev" ] && echo none > "$dev"; done' | sudo tee -a /etc/rc.local

For traditional spinning disks, keep the default mq-deadline scheduler, which optimizes request ordering to minimize seek overhead.

Firewall Configuration with nftables

Void includes nftables as the preferred firewall framework. A properly configured firewall not only secures the server but can also mitigate certain denial-of-service patterns by rate-limiting connections.

sudo xbps-install -S nftables
sudo ln -s /etc/sv/nftables /var/service/nftables

sudo tee /etc/nftables.conf << 'EOF'
#!/usr/sbin/nft -f

flush ruleset

table inet webserver {
    set blacklist {
        type ipv4_addr
        flags timeout
        timeout 1h
    }

    chain input {
        type filter hook input priority 0; policy drop;

        # Allow loopback
        iif "lo" accept

        # Allow established connections
        ct state established,related accept

        # Allow SSH with rate limiting
        tcp dport 22 ct state new limit rate 5/minute burst 10 packets accept

        # Allow HTTP and HTTPS
        tcp dport { 80, 443 } accept

        # Drop invalid packets
        ct state invalid drop

        # ICMP rate limiting
        icmp type echo-request limit rate 1/second accept

        # Drop everything else
        counter drop
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}
EOF

sudo nft -f /etc/nftables.conf
sudo sv restart nftables

Memory and Swap Configuration

For a dedicated web server, swap should exist as a safety net but should rarely be used. The vm.swappiness setting in the sysctl configuration already addresses this. Additionally, consider using zram for compressed swap in RAM, which is faster than disk swap and useful for handling memory spikes.

sudo xbps-install -S zram

# Create a zram swap service
sudo mkdir -p /etc/sv/zram-swap

sudo tee /etc/sv/zram-swap/run << 'EOF'
#!/bin/sh
# Create a 2GB zram device
modprobe zram num_devices=1
echo lz4 > /sys/block/zram0/comp_algorithm
echo 2G > /sys/block/zram0/disksize
mkswap /dev/zram0
swapon -p 100 /dev/zram0

# Keep the service running
exec chpst -b zram-swap pause
EOF

sudo tee /etc/sv/zram-swap/finish << 'EOF'
#!/bin/sh
swapoff /dev/zram0 2>/dev/null
echo 1 > /sys/block/zram0/reset 2>/dev/null
EOF

sudo chmod +x /etc/sv/zram-swap/run /etc/sv/zram-swap/finish
sudo ln -s /etc/sv/zram-swap /var/service/zram-swap

The lz4 compression algorithm offers an excellent balance between compression ratio and CPU overhead, making it ideal for zram swap on production servers.

Monitoring and Ongoing Maintenance

Tuning is not a one-time activity. Continuously monitor the server to detect regressions and identify new bottlenecks as traffic patterns evolve.

Setting Up Lightweight Monitoring

# Install monitoring tools
sudo xbps-install -S sysstat procps-ng

# Enable sysstat data collection
sudo sed -i 's/false/true/' /etc/default/sysstat
sudo ln -s /etc/sv/sysstat /var/service/sysstat

# Create a simple monitoring script
sudo tee /usr/local/bin/server-health.sh << 'EOF'
#!/bin/sh
echo "=== System Load ==="
uptime

echo -e "\n=== Memory ==="
free -h

echo -e "\n=== Top Processes by CPU ==="
ps aux --sort=-%cpu | head -10

echo -e "\n=== Top Processes by Memory ==="
ps aux --sort=-%mem | head -10

echo -e "\n=== Network Connections ==="
ss -s

echo -e "\n=== Disk Usage ==="
df -h

echo -e "\n=== Nginx Connections ==="
ss -tnp | grep nginx | wc -l
EOF

sudo chmod +x /usr/local/bin/server-health.sh

Log Rotation

Logs can fill disk space and cause performance degradation. Configure log rotation to keep logs manageable.

sudo xbps-install -S logrotate

sudo tee /etc/logrotate.d/webserver << 'EOF'
/var/log/nginx/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    postrotate
        sv reload nginx 2>/dev/null || true
    endscript
}

/var/log/php-fpm/*.log {
    weekly
    rotate 8
    compress
    delaycompress
    missingok
    notifempty
    postrotate
        sv reload php-fpm 2>/dev/null || true
    endscript
}
EOF

# Add a daily cron-like runit service for logrotate
sudo mkdir -p /etc/sv/logrotate-daily

sudo tee /etc/sv/logrotate-daily/run << 'EOF'
#!/bin/sh
while true; do
    sleep 86400
    /usr/sbin/logrotate /etc/logrotate.conf
done
EOF

sudo chmod +x /etc/sv/logrotate-daily/run
sudo ln -s /etc/sv/logrotate-daily /var/service/logrotate-daily

Best Practices Summary

Load Testing Your Configuration

After applying all tuning changes, run load tests to validate the improvements.

# Basic Apache Bench test - 1000 requests, 100 concurrent
ab -n 1000 -c 100 http://localhost/

# More realistic wrk test - 30 seconds, 50 threads, 200 connections
wrk -t 50 -c 200 -d 30s http://localhost/

# Test with keepalive
wrk -t 50 -c 200 -d 30s --latency http://localhost/

Compare the requests-per-second and latency percentiles against your baseline. A well-tuned Void Linux server should show significantly higher throughput and lower p99 latency than the default configuration.

Conclusion

Void Linux provides an excellent foundation for high-performance web servers thanks to its minimal footprint, fast boot times, and the simplicity of runit service management. By systematically tuning the kernel network stack, raising file descriptor limits, configuring Nginx and PHP-FPM to match your hardware, optimizing database parameters, disabling Transparent Huge Pages, selecting the right I/O scheduler, and setting up a lightweight firewall with rate limiting, you can extract substantially more performance from the same hardware. The key principles are to measure first, change one variable at a time, persist all configurations, and monitor continuously. With these practices in place, your Void Linux web server will handle traffic efficiently, respond with low latency, and remain stable under load.

— Ad —

Google AdSense will appear here after approval

← Back to all articles