Validator Server Hardening Guide
Production validators are high-value targets. A compromised validator can be jailed, have its stake at risk, or be used to attack the network. This guide covers the security hardening every Vexidus validator should apply before going live.
Quick Hardening Checklist
| Step | Priority | Time |
|---|---|---|
| Firewall (UFW) | Critical | 5 min |
| SSH Hardening | Critical | 10 min |
| Fail2ban | Critical | 5 min |
| Dedicated User | Critical | 5 min |
| Automatic Updates | High | 2 min |
| RPC Lockdown | High | 5 min |
| Key Permissions | High | 2 min |
| Systemd Hardening | Medium | 5 min |
| Log Monitoring | Medium | 10 min |
| Backup Strategy | Medium | 15 min |
Firewall (UFW)
Every validator must run a firewall. The principle: deny everything, allow only what's needed.
Install and configure
sudo apt update && sudo apt install -y ufw
# Default policy: deny all incoming, allow all outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (required for remote management)
sudo ufw allow 22/tcp
# Allow P2P (required for consensus — QUIC uses UDP)
# Use your actual P2P port (default 9944)
sudo ufw allow 9944/udp
# Allow HTTP/HTTPS only if you're running a web server (explorer, docs, etc.)
# Most validators do NOT need these:
# sudo ufw allow 80/tcp
# sudo ufw allow 443/tcp
# Enable the firewall
sudo ufw enable
sudo ufw status verbose
What NOT to expose
| Port | Service | Expose? |
|---|---|---|
| 9944/udp | P2P (QUIC) | Yes — required for consensus |
| 9933/tcp | RPC | No — localhost only (see RPC Lockdown) |
| 9100/tcp | Prometheus metrics | No — localhost only |
| 22/tcp | SSH | Yes — but harden it (see below) |
Verify
sudo ufw status numbered
You should see only SSH and your P2P port allowed.
SSH Hardening
SSH is the #1 attack vector for internet-facing servers. These settings eliminate the most common attacks.
Prerequisites
Before changing SSH config, ensure you have key-based auth working:
# On your LOCAL machine, generate a key if you don't have one
ssh-keygen -t ed25519 -C "validator-admin"
# Copy your public key to the server
ssh-copy-id -i ~/.ssh/id_ed25519.pub your_user@your_server_ip
# Test key-based login (should not ask for password)
ssh -i ~/.ssh/id_ed25519 your_user@your_server_ip
Do NOT disable password auth until you've confirmed key-based auth works. You will lock yourself out.
Apply hardening
Edit /etc/ssh/sshd_config:
sudo nano /etc/ssh/sshd_config
Set these values (uncomment if needed):
# Disable password authentication (key-only)
PasswordAuthentication no
ChallengeResponseAuthentication no
# Disable root login (use a regular user + sudo)
PermitRootLogin no
# Limit authentication attempts
MaxAuthTries 3
MaxSessions 3
# Disable unused features
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
# Use only protocol 2
Protocol 2
# Idle timeout (disconnect after 10 minutes of inactivity)
ClientAliveInterval 300
ClientAliveCountMax 2
If your server only has a root user (no separate admin user), set PermitRootLogin prohibit-password instead of no — this allows key-based root login while still blocking password auth.
Check for cloud-init override
Some cloud providers (OVH, Hetzner) override SSH settings via cloud-init. Check:
ls /etc/ssh/sshd_config.d/
cat /etc/ssh/sshd_config.d/*.conf 2>/dev/null
If you see PasswordAuthentication yes in any file there, change it to no:
sudo sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config.d/*.conf
Restart SSH
# Keep your current session open! Test in a NEW terminal first.
sudo systemctl restart sshd
# In a NEW terminal, verify you can still log in:
ssh -i ~/.ssh/id_ed25519 your_user@your_server_ip
Fail2ban
Fail2ban automatically bans IPs that show malicious behavior (brute-force SSH attempts, etc.).
Install and configure
sudo apt install -y fail2ban
Create /etc/fail2ban/jail.local:
[DEFAULT]
bantime = 86400
findtime = 600
maxretry = 3
backend = systemd
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400
This bans any IP that fails SSH login 3 times within 10 minutes, for 24 hours.
Start and enable
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Verify
sudo fail2ban-client status sshd
Check banned IPs
sudo fail2ban-client status sshd
Unban an IP (if you accidentally lock yourself out from another machine)
sudo fail2ban-client set sshd unbanip YOUR_IP
Dedicated System User
Never run your validator as root. Create a dedicated system user with no login shell:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin vexidus
sudo mkdir -p /opt/vexidus/data
sudo chown -R vexidus:vexidus /opt/vexidus
Your systemd service should specify User=vexidus and Group=vexidus.
Automatic Security Updates
Enable unattended security patches so critical vulnerabilities are patched automatically:
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Verify it's active:
cat /etc/apt/apt.conf.d/20auto-upgrades
You should see:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
RPC Lockdown
Your RPC port (default 9933) gives full read access to chain state and can submit transactions. Never expose it to the public internet.
Option A: Localhost only (recommended)
The firewall config above already blocks port 9933. If you need to query RPC remotely, use an SSH tunnel:
# From your local machine
ssh -L 9933:localhost:9933 your_user@your_server_ip
# Now query via localhost on your machine
curl http://localhost:9933 -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
Option B: Nginx reverse proxy with rate limiting
If you need to expose RPC publicly (e.g., for an explorer), put it behind nginx with rate limiting:
# /etc/nginx/sites-available/vexidus-rpc
upstream vexidus_rpc {
server 127.0.0.1:9933;
}
limit_req_zone $binary_remote_addr zone=rpc:10m rate=30r/s;
server {
listen 443 ssl;
server_name rpc.yourvalidator.com;
ssl_certificate /etc/letsencrypt/live/rpc.yourvalidator.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/rpc.yourvalidator.com/privkey.pem;
location / {
limit_req zone=rpc burst=60 nodelay;
proxy_pass http://vexidus_rpc;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
This limits each IP to 30 requests/second with a burst of 60. Adjust based on your expected traffic.
Key File Permissions
Your validator key is the most sensitive file on the server. Lock it down:
# Owned by the vexidus user, readable only by owner
sudo chown vexidus:vexidus /opt/vexidus/validator.key
sudo chmod 600 /opt/vexidus/validator.key
# Verify
ls -la /opt/vexidus/validator.key
# Should show: -rw------- 1 vexidus vexidus
Also secure any .env files or configuration files that contain sensitive data:
sudo chmod 600 /opt/vexidus/validator.toml
Systemd Hardening
Add security directives to your systemd service file (/etc/systemd/system/vexidus-validator.service):
[Service]
# ... your existing ExecStart, User, etc. ...
# Security hardening
ProtectSystem=full
ProtectHome=read-only
NoNewPrivileges=true
PrivateTmp=true
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictSUIDSGID=true
# File descriptor limit (RocksDB needs many open files)
LimitNOFILE=65536
After editing:
sudo systemctl daemon-reload
sudo systemctl restart vexidus-validator
Log Monitoring
Watch for suspicious activity
# Failed SSH attempts
sudo grep "Failed password" /var/log/auth.log | tail -20
# Successful logins
sudo grep "Accepted" /var/log/auth.log | tail -20
# fail2ban actions
sudo grep "Ban" /var/log/fail2ban.log | tail -20
Simple alerting script
Save as /opt/vexidus/alert-check.sh:
#!/bin/bash
# Run via cron every 5 minutes: */5 * * * * /opt/vexidus/alert-check.sh
RPC="http://localhost:9933"
THRESHOLD=60 # Alert if no new block in 60 seconds
# Check if node is running
if ! systemctl is-active --quiet vexidus-validator; then
echo "ALERT: vexidus-validator is DOWN" | logger -t vexidus-alert
exit 1
fi
# Check block production
BLOCK=$(curl -sf "$RPC" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"vex_getNetworkStats","params":[],"id":1}' \
| jq -r '.result.blockHeight // 0')
if [ "$BLOCK" = "0" ]; then
echo "ALERT: RPC unreachable or returning 0 blocks" | logger -t vexidus-alert
fi
# Check disk usage
DISK_PCT=$(df /opt/vexidus/data | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$DISK_PCT" -gt 85 ]; then
echo "ALERT: Disk usage at ${DISK_PCT}%" | logger -t vexidus-alert
fi
chmod +x /opt/vexidus/alert-check.sh
# Add to cron
(crontab -l 2>/dev/null; echo "*/5 * * * * /opt/vexidus/alert-check.sh") | crontab -
For production alerting, integrate with services like PagerDuty, Grafana alerts, or a simple webhook to Slack/Discord.
Backup Strategy
What to back up
| Item | Location | Frequency | Critical? |
|---|---|---|---|
| Validator key | /opt/vexidus/validator.key | Once (store offline) | YES — loss = unrecoverable stake |
| Node identity | /opt/vexidus/data/node_key | Once | Preserves PeerId across restarts |
| Chain state | /opt/vexidus/data/ | Weekly | Can re-sync from peers if lost |
| Config files | /opt/vexidus/validator.toml, systemd service | On change | Easy to recreate |
Automated backup script
#!/bin/bash
# /opt/vexidus/backup.sh — run weekly via cron
BACKUP_DIR="/opt/vexidus/backups"
DATE=$(date +%Y%m%d)
mkdir -p "$BACKUP_DIR"
# Back up config and key (encrypted)
tar czf "$BACKUP_DIR/config-$DATE.tar.gz" \
/opt/vexidus/validator.key \
/opt/vexidus/validator.toml \
/etc/systemd/system/vexidus-validator.service
# Keep only last 4 backups
ls -t "$BACKUP_DIR"/config-*.tar.gz | tail -n +5 | xargs rm -f 2>/dev/null
echo "Backup complete: $BACKUP_DIR/config-$DATE.tar.gz"
Store an encrypted copy of your validator key in a separate location (encrypted USB, password manager, or cloud storage with client-side encryption). Never store keys in plaintext on cloud services.
Advanced Hardening (Optional)
Restrict SSH to specific IPs
If you always connect from the same IP or VPN:
sudo ufw delete allow 22/tcp
sudo ufw allow from YOUR_HOME_IP to any port 22 proto tcp
Disable IPv6 (if not needed)
echo "net.ipv6.conf.all.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Install and run Lynis (security audit)
sudo apt install -y lynis
sudo lynis audit system
Review the report and address any warnings relevant to your setup.
Rate limit SSH connections at the firewall level
# Limit SSH to 6 connection attempts per 30 seconds per IP
sudo ufw limit ssh
This is in addition to fail2ban and provides defense-in-depth.
Verification Checklist
After hardening, verify everything is correct:
# Firewall is active and configured
sudo ufw status verbose
# SSH password auth is disabled
sudo sshd -T | grep passwordauthentication
# Should output: passwordauthentication no
# Fail2ban is running
sudo fail2ban-client status sshd
# Node is running as dedicated user
ps aux | grep vexidus-node | grep -v grep
# Should show 'vexidus' user, NOT root
# Key file has correct permissions
ls -la /opt/vexidus/validator.key
# Should show: -rw------- 1 vexidus vexidus
# RPC is not externally accessible
# From another machine:
curl --connect-timeout 5 http://YOUR_SERVER_IP:9933
# Should timeout or connection refused
# P2P port is open
# From another machine:
nc -zuv YOUR_SERVER_IP 9944
# Should show connection succeeded
Summary
| Layer | What | Why |
|---|---|---|
| Firewall | UFW deny-all + whitelist P2P/SSH | Blocks all unnecessary ports |
| SSH | Key-only, no root, rate limited | Eliminates brute-force attacks |
| Fail2ban | Auto-ban after 3 failures | Blocks persistent attackers |
| User isolation | Dedicated vexidus user | Limits blast radius of compromise |
| RPC lockdown | Localhost only | Prevents unauthorized chain queries/transactions |
| Auto-updates | Unattended security patches | Closes known OS vulnerabilities |
| Monitoring | Log checks + alerting | Early detection of issues |
| Backups | Key + config, offsite | Recovery from data loss |
These measures follow the principle of defense-in-depth — no single layer is sufficient alone, but together they make your validator significantly harder to compromise.
Questions? Join the Vexidus validator community or open an issue at github.com/Vexidus-Labs/Vexidus.