Introduction to Void Linux for Servers
Void Linux is an independent, rolling-release distribution built from scratch — not derived from Debian, Red Hat, or Arch. It distinguishes itself through two core design choices: the XBPS (X Binary Package System) package manager and the runit init system. For server workloads, these choices translate into a lean, fast, and predictable operating system that boots quickly, consumes minimal resources, and avoids the complexity of systemd.
This tutorial walks through a complete Void Linux server setup — from a fresh installation to a hardened, production-ready deployment. By the end, you will have a system capable of hosting web applications, containers, or infrastructure services with confidence.
Why Choose Void Linux for Servers
Key Advantages
- runit init system: Simple, fast, and scriptable. Services are plain directories with executable scripts — no opaque binary state.
- XBPS package manager: Fast, reliable, and supports atomic transactions with easy rollback.
- Rolling release: No major version upgrades to plan around. Updates are incremental.
- musl libc option: A musl-based variant offers a smaller attack surface and lower memory footprint.
- No systemd: Avoids the sprawling complexity and dependency chain of systemd, which matters in minimal server environments.
- Independent: Not tied to any corporate sponsor or upstream distribution policy.
When Void Is a Good Fit
Void excels on VPS instances, containers, embedded systems, and any environment where you want tight control over what runs. It is less ideal if you need extensive vendor support, certified compliance, or a vast third-party repository of prebuilt commercial software.
Installation
Obtaining the Installer
Download the appropriate ISO from the official Void Linux website. For servers, the base image (not the XFCE desktop edition) is preferred. Choose the glibc variant unless you have a specific reason to use musl.
# Verify the download integrity
sha256sum void-x86_64-ROOTFS-20231018.tar.xz
# Compare against the SHA256SUMS file from the mirror
Booting and Partitioning
Boot the installer media. The Void installer is a TUI called void-installer. Before launching it, partition your disk manually for full control.
# Identify the target disk
lsblk
# Partition with fdisk or cfdisk
cfdisk /dev/sda
# Example layout:
# /dev/sda1 512M EFI System (if UEFI)
# /dev/sda2 4G Linux swap
# /dev/sda3 rest Linux filesystem
# Format the partitions
mkfs.fat -F32 /dev/sda1
mkswap /dev/sda2
mkfs.ext4 /dev/sda3
# Activate swap
swapon /dev/sda2
Running the Installer
Launch the installer and follow the prompts:
void-installer
Within the installer, configure the following:
- Keyboard: Select your layout (e.g.,
us). - Network: Configure DHCP or static IP.
- Source: Choose
Networkto pull the latest packages. - Hostname: Set a meaningful name like
prod-web-01. - Locale: Select
en_US.UTF-8or your preferred locale. - Timezone: Set correctly (e.g.,
America/New_York). - Root password: Set a strong password.
- User account: Create a non-root admin user.
- Partitions: Assign mount points to the partitions you created.
- Bootloader: Choose
GRUBfor BIOS orGRUB-EFIfor UEFI.
After confirming, the installer downloads and installs the base system. Reboot when complete.
Post-Installation Configuration
First Boot and Updates
Log in as root and immediately bring the system up to date:
# Sync and update all packages
xbps-install -Su
# If a kernel update occurred, reboot
reboot
Installing Essential Packages
# Core utilities for server administration
xbps-install -S sudo vim curl wget git htop tmux \
openssh chrony rsync ufw fail2ban
# Development and build tools (optional)
xbps-install -S base-devel
Package Management with XBPS
Core Commands
# Search for a package
xbps-query -Rs nginx
# Install a package
xbps-install -S nginx
# Remove a package (keeping config)
xbps-remove nginx
# Remove a package and its dependencies
xbps-remove -R nginx
# List installed packages
xbps-query -l
# Show package information
xbps-query -RS nginx
# Clean the package cache
xbps-remove -O
# Remove orphaned dependencies
xbps-remove -o
Repository Configuration
Void uses signed repositories. The default configuration lives in /usr/share/xbps.d. To override or add repositories, create files in /etc/xbps.d:
# Enable the non-free repository (if needed)
mkdir -p /etc/xbps.d
cp /usr/share/xbps.d/*-repository-*.conf /etc/xbps.d/
# Edit to point to non-free
sed -i 's|repo-default|repo-default/nonfree|' /etc/xbps.d/*-repository-*.conf
# Re-sync
xbps-install -S
Handling Updates Safely
Because Void is rolling, updates should be applied regularly but carefully on production systems:
# Check what would be updated without applying
xbps-install -Sun
# Apply updates
xbps-install -Su
# If the update touches critical packages, sync filesystems and reboot
sync && reboot
Service Management with runit
Understanding the runit Model
Void uses runit as its init and service supervisor. Services live in /etc/sv/, and enabled services are symlinked into /var/service/. Each service is a directory containing at minimum an executable run script.
Enabling and Managing Services
# Enable a service (creates symlink in /var/service)
ln -s /etc/sv/sshd /var/service/
# Disable a service
rm /var/service/sshd
# Check service status
sv status sshd
# Start, stop, restart
sv up sshd
sv down sshd
sv restart sshd
# Check status of all services
sv status /var/service/*
Creating a Custom Service
Suppose you have a Node.js application you want to supervise. Create a service directory:
mkdir -p /etc/sv/myapp
Create the run script:
#!/bin/sh
# /etc/sv/myapp/run
exec 2>&1
cd /opt/myapp
exec chpst -u myappuser:myappuser node server.js
Make it executable and enable it:
chmod +x /etc/sv/myapp/run
ln -s /etc/sv/myapp /var/service/
# Verify it is running
sv status myapp
Logging with runit
runit pairs each service with an optional logging service. Create a log subdirectory:
mkdir -p /etc/sv/myapp/log
Create /etc/sv/myapp/log/run:
#!/bin/sh
exec svlogd /var/log/myapp
mkdir -p /var/log/myapp
chmod +x /etc/sv/myapp/log/run
Now stdout and stderr from your service are captured to /var/log/myapp/current with automatic rotation.
User Management and Sudo
Creating a Non-Root Admin User
# Create a user with a home directory and bash shell
useradd -m -s /bin/bash deploy
# Set a password
passwd deploy
# Add to the wheel group for sudo access
usermod -aG wheel deploy
Configuring Sudo
Uncomment the wheel group in the sudoers file:
visudo
Find and uncomment:
%wheel ALL=(ALL:ALL) ALL
For passwordless sudo (use with caution), use:
%wheel ALL=(ALL:ALL) NOPASSWD: ALL
SSH Hardening
SSH is the primary entry point to your server. Harden it immediately.
Generating SSH Keys
On your local machine, generate a key pair:
ssh-keygen -t ed25519 -C "admin@production"
Copy the public key to the server:
ssh-copy-id deploy@your-server-ip
Securing sshd Configuration
Edit /etc/ssh/sshd_config:
# Disable root login
PermitRootLogin no
# Disable password authentication
PasswordAuthentication no
PubkeyAuthentication yes
# Limit to specific users
AllowUsers deploy
# Change default port (optional but reduces noise)
Port 2222
# Disable empty passwords
PermitEmptyPasswords no
# Limit authentication attempts
MaxAuthTries 3
# Disable X11 forwarding on servers
X11Forwarding no
Restart sshd:
sv restart sshd
Always test your new connection in a separate terminal before closing your existing session.
Firewall Configuration
Using UFW
UFW (Uncomplicated Firewall) provides a simple interface to iptables:
# Default policies
ufw default deny incoming
ufw default allow outgoing
# Allow SSH (use your custom port if changed)
ufw allow 2222/tcp
# Allow HTTP and HTTPS
ufw allow 80/tcp
ufw allow 443/tcp
# Enable the firewall
ufw enable
# Check status
ufw status verbose
Rate Limiting SSH
# Limit SSH to 6 connections per 30 seconds per IP
ufw limit 2222/tcp
Time Synchronization
Accurate time is critical for logs, TLS certificates, and distributed systems. Install and enable chrony:
xbps-install -S chrony
ln -s /etc/sv/chronyd /var/service/
# Verify synchronization
chronyc tracking
Automatic Security Updates
While Void does not ship an automatic updater by default, you can schedule regular update checks with cron or a runit service. A conservative approach is a weekly notification rather than automatic application:
# Install cron
xbps-install -S cronie
ln -s /etc/sv/cronie /var/service/
# Add a weekly update check
cat > /etc/cron.weekly/xbps-check << 'EOF'
#!/bin/sh
UPDATES=$(xbps-install -Sun | wc -l)
if [ "$UPDATES" -gt 0 ]; then
echo "$UPDATES packages need updating on $(hostname)" | \
mail -s "Void update notification" admin@example.com
fi
EOF
chmod +x /etc/cron.weekly/xbps-check
Setting Up a Web Server
Installing Nginx
xbps-install -S nginx
ln -s /etc/sv/nginx /var/service/
Basic Site Configuration
Create a server block in /etc/nginx/conf.d/example.com.conf:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/example.access.log;
error_log /var/log/nginx/example.error.log;
}
# Create the web root
mkdir -p /var/www/example.com
echo "<h1>Hello from Void Linux</h1>" > /var/www/example.com/index.html
# Test and reload
nginx -t
sv restart nginx
Installing Certbot for TLS
xbps-install -S certbot
certbot --nginx -d example.com -d www.example.com
Certbot modifies the Nginx configuration automatically and sets up a renewal cron entry.
Deploying Applications with Docker
# Install Docker
xbps-install -S docker docker-compose
# Enable and start the Docker daemon
ln -s /etc/sv/docker /var/service/
# Add your deploy user to the docker group
usermod -aG docker deploy
# Verify
docker --version
docker run hello-world
System Monitoring and Maintenance
Resource Monitoring
# Real-time process view
htop
# Disk usage
df -h
# Memory usage
free -h
# Network connections
ss -tulnp
# Open files
lsof -i
Log Management
Void logs to /var/log/. The kernel ring buffer is accessible via dmesg. Service logs managed by runit appear in their respective /var/log/<service>/ directories.
# View recent kernel messages
dmesg -T | tail -50
# Follow a service log
tail -f /var/log/nginx/access.log
# View runit-managed logs
cat /var/log/sshd/current
Installing a System Monitor
For lightweight metrics collection, install netdata:
xbps-install -S netdata
ln -s /etc/sv/netdata /var/service/
Access the dashboard at http://your-server-ip:19999. Remember to firewall this port or proxy it behind Nginx with authentication.
Backup Strategy
Configuration Backups
Regularly back up critical directories:
#!/bin/sh
# /usr/local/bin/backup-config.sh
DATE=$(date +%Y%m%d)
tar czf /backups/config-$DATE.tar.gz \
/etc \
/var/www \
/opt/myapp \
2>/dev/null
# Retain only the last 7 backups
find /backups -name "config-*.tar.gz" -mtime +7 -delete
chmod +x /usr/local/bin/backup-config.sh
# Schedule daily
echo "0 2 * * * /usr/local/bin/backup-config.sh" | crontab -
Database Backups
#!/bin/sh
# /usr/local/bin/backup-postgres.sh
DATE=$(date +%Y%m%d)
PGPASSWORD=$DB_PASSWORD pg_dump -U postgres -Fc mydb > /backups/mydb-$DATE.dump
find /backups -name "mydb-*.dump" -mtime +14 -delete
Best Practices for Production
Security Hardening Checklist
- Disable root SSH login and password authentication.
- Use a non-standard SSH port to reduce automated scanning noise.
- Install and configure
fail2banto ban repeated failed login attempts. - Keep the system updated on a regular schedule.
- Run services as non-root users with minimal privileges.
- Use a firewall with default-deny inbound policy.
- Remove unused packages to reduce attack surface.
- Enable disk encryption on physical servers where feasible.
- Use separate users for different applications.
- Audit open ports regularly with
ss -tulnp.
Configuring Fail2ban
xbps-install -S fail2ban
ln -s /etc/sv/fail2ban /var/service/
# Create a local configuration
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
[sshd]
enabled = true
port = 2222
logpath = /var/log/sshd/current
EOF
sv restart fail2ban
fail2ban-client status sshd
Kernel and System Tuning
For production workloads, tune kernel parameters in /etc/sysctl.d/99-production.conf:
# Increase file descriptor limits
fs.file-max = 2097152
# Network tuning
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10000 65535
# Security
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Apply changes
sysctl --system
Raise the open file limit in /etc/security/limits.conf:
* soft nofile 65535
* hard nofile 65535
root soft nofile 65535
root hard nofile 65535
Disabling Unnecessary Services
# List all enabled services
ls -la /var/service/
# Remove anything you do not need
rm /var/service/unused-service
Documentation and Reproducibility
Maintain a provisioning script or configuration management playbook. Even a simple shell script that records every step makes future deployments reproducible:
#!/bin/sh
# provision.sh - Void Linux server provisioning
set -e
xbps-install -Su
xbps-install -S sudo vim curl openssh chrony ufw fail2ban nginx
# Enable services
ln -s /etc/sv/sshd /var/service/
ln -s /etc/sv/chronyd /var/service/
ln -s /etc/sv/nginx /var/service/
ln -s /etc/sv/fail2ban /var/service/
# Firewall
ufw default deny incoming
ufw default allow outgoing
ufw allow 2222/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
echo "Provisioning complete. Reboot recommended."
Conclusion
Void Linux offers a refreshingly straightforward server platform. Its runit init system makes service management transparent and scriptable, XBPS handles packages with speed and reliability, and the absence of systemd keeps the system lean and understandable. By following this tutorial, you now have a hardened, production-ready Void Linux server with proper user management, SSH hardening, firewall protection, automatic logging, web serving capability, and a documented maintenance routine. The key to long-term stability on a rolling-release distribution is consistency: apply updates regularly, monitor logs proactively, and keep your configuration reproducible through scripts or infrastructure-as-code tooling. With these practices in place, Void Linux provides a dependable foundation for production workloads of nearly any scale.