Server hardening¶
The default Ubuntu install is not safe for a public-facing Bitcoin Gold service. This course walks you through the changes you need to make on any host that runs:
- A public
bgolddfull node (P2P on8338) - A public Electrum server (
50001/50002/50003) - A public Blockbook indexer (via
nginxon443) - A public DNS seeder (UDP/53)
- A mining pool (Stratum on
3333/3334/3335)
The end state is a reusable baseline for a hardened public host.
1. OS basics¶
Keep the system up to date¶
Enable unattended-upgrades for security patches:
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades # answer "Yes"
Verify the timer is active:
NTP¶
A node that drifts more than ~10 minutes past real time will reject blocks. systemd-timesyncd is the default.
Disable root login over SSH¶
sudo nano /etc/ssh/sshd_config
# Set:
# PermitRootLogin no
# PasswordAuthentication no
# KbdInteractiveAuthentication no
sudo sshd -t && sudo systemctl reload ssh
Test before closing your current session
If you break SSH while locked out as root, recovery is via the hypervisor console. Test in a second session first.
2. Firewall: ufw¶
ufw is the simplest path to a sane default-deny firewall on Ubuntu. The pattern:
Open only what you actually expose¶
| Service | Ports | Comment |
|---|---|---|
| SSH | 22/tcp or <your-port>/tcp |
Key-only authentication is essential; a custom port is optional. |
| Bitcoin Gold P2P | 8338/tcp |
"Bitcoin Gold P2P" |
| Bitcoin Gold testnet P2P | 18338/tcp |
only if you run testnet |
| ElectrumX TLS | 50002/tcp |
"Electrum TLS" |
| ElectrumX WSS | 50003/tcp |
"Electrum WSS" |
| Stratum | 3333-3335/tcp |
"Pool" (if applicable) |
| DNS | 53/udp + 53/tcp |
"DNS" (only on the seeder host) |
| HTTP/HTTPS | 80, 443/tcp |
"Web" (only on the Blockbook / web host) |
Example for a full public-infrastructure host:
sudo ufw allow 8338/tcp comment "Bitcoin Gold P2P"
sudo ufw allow 50002/tcp comment "ElectrumX SSL"
sudo ufw allow 50003/tcp comment "ElectrumX WS"
sudo ufw allow 3333/tcp comment "Stratum"
sudo ufw enable
sudo ufw status verbose
Don't open 8332 (RPC) to the network
RPC is for local services only. bitcoingold.conf should have rpcbind=127.0.0.1 and rpcallowip=127.0.0.1. Never open 8332 in ufw.
3. SSH hardening¶
Beyond disabling root login:
- Move SSH to a non-standard port (e.g. 2222 or 4444). Reduces noise from automated scanners.
- Use key-only auth (no passwords).
- Limit users with
AllowUsersif the host is single-purpose.
# /etc/ssh/sshd_config.d/hardening.conf
Port 4444
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
X11Forwarding no
AllowTcpForwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTries 3
LoginGraceTime 30
Reload:
4. fail2ban¶
Even with key-only auth, monitoring SSH and any web admin endpoint for failed authentications is worth it.
sudo apt install -y fail2ban
sudo tee /etc/fail2ban/jail.d/btg.conf > /dev/null <<'EOF'
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = 4444
logpath = %(sshd_log)s
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
EOF
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
5. systemd sandboxing¶
Every service in the operator courses uses these directives — they cost nothing and significantly reduce blast radius if the daemon is compromised.
[Service]
# Don't gain new privileges
NoNewPrivileges=true
# Private /tmp and /var/tmp
PrivateTmp=true
# /usr, /boot, /etc read-only
ProtectSystem=full
# /home, /root, /run/user inaccessible
ProtectHome=true
# Only allow writes to specific paths
ReadWritePaths=/var/lib/bitcoingold
# /dev only contains pseudo-devices
PrivateDevices=true
# Cannot create new executable memory mappings
MemoryDenyWriteExecute=true
# Restrict syscall set
SystemCallArchitectures=native
SystemCallFilter=@system-service
# Drop dangerous capabilities
CapabilityBoundingSet=
AmbientCapabilities=
For services that need to bind a privileged port (e.g. DNS seeder on 53), keep only CAP_NET_BIND_SERVICE:
6. Auto-updates (security only)¶
Configure unattended-upgrades for security updates, verify its allowed origins, and monitor whether an update requires a reboot. Test broader automatic updates before enabling them on an infrastructure host.
For the full node itself, don't auto-update bgoldd — see the Full node course for the manual upgrade flow. The reason: a forced upgrade during a reorg can leave the daemon on a stale chain briefly.
7. Certificate renewal¶
Wildcard certificates require DNS-01 validation. Use a narrowly scoped API token for your DNS provider and verify unattended renewal before relying on the certificate:
# Switch to the DNS-01 challenge via Cloudflare (or your DNS provider)
sudo snap install certbot-dns-cloudflare
sudo tee /etc/letsencrypt/cloudflare.ini > /dev/null <<'EOF'
dns_cloudflare_api_token = <your-CF-token>
EOF
sudo chmod 600 /etc/letsencrypt/cloudflare.ini
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d example.com \
-d "*.example.com"
Enable the renewal timer and test renewal:
sudo systemctl unmask snap.certbot.renew.timer
sudo systemctl enable --now snap.certbot.renew.timer
sudo systemctl list-timers | grep certbot
sudo certbot renew --dry-run
For subdomains that don't fit the wildcard, expose port 80 to Let's Encrypt's HTTP-01 challenge (the ufw allow 80/tcp rule is enough).
8. Logrotate¶
Daemons that write a lot to disk (full node, ElectrumX, Blockbook) need their logs capped. See the per-service logrotate config in each course.
The system-wide default (/etc/logrotate.d/rsyslog) handles syslog; for application logs you need explicit configs.
9. AppArmor (optional)¶
Ubuntu 22.04+ has AppArmor by default. For daemons, write a profile that denies anything not in the service's expected path set. The aa-status tool shows which profiles are loaded.
A practical minimum for the Bitcoin Gold node:
sudo apt install -y apparmor-utils
sudo aa-genprof /usr/local/bin/bgoldd
# Interactive — answer prompts to record then enforce
Build and test the profile in a staging environment before enforcing it on a public service.
10. What a hardened Bitcoin Gold host looks like¶
| Layer | Setting |
|---|---|
| OS | Ubuntu 22.04 LTS, security updates auto-applied |
| NTP | systemd-timesyncd, drift < 100 ms |
| Firewall | ufw default deny, only service ports open |
| SSH | Non-standard port, key-only, no root |
| fail2ban | Active for SSH and nginx authentication failures |
| Service sandboxing | NoNewPrivileges, PrivateTmp, ProtectSystem=full, ReadWritePaths only |
| TLS | Let's Encrypt certificate with tested unattended renewal |
| Monitoring | systemd journal, logrotate, and fail2ban logs; optionally Prometheus and Grafana |
| Backups | Encrypted, off-host copies of wallet data, service configuration, and any unique application state |
Verification checklist¶
After applying this course, confirm:
-
apt list --upgradableshows no security updates -
timedatectlshows NTP synchronized -
sudo ufw status verbosematches your intent -
sshd -T | grep -E 'port|passwordauth|permitroot'shows your hardened values -
systemctl status fail2banshows active (if you've completed section 4) - Each service's
systemctl show <unit> -p NoNewPrivileges,PrivateTmpshows the directives applied -
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -datesshows a certificate that is not within 30 days of expiry, andsystemctl list-timers | grep certbotshows a healthy renewal timer
What to do next¶
- Wire monitoring in: Prometheus node-exporter + blackbox-exporter for the P2P port, Grafana dashboards.
- Add alerting: dead-man-switch via
healthchecks.ioor a self-hosted equivalent. - Set up and restore-test backups for wallet data and any unique service state.
- Test disaster recovery: a restored node should re-sync from the network, not from a corrupted chainstate.
- Add a SIEM if you operate > 3 hosts — even a simple Loki + Grafana stack catches most anomalies.