1. A normal user with sudo
adduser deploy
usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh && chmod 600 /home/deploy/.ssh/authorized_keys
Open a second terminal and confirm ssh deploy@server and sudo -v work before you go on.
2. SSH with keys only
Create a drop-in file so package updates do not overwrite your settings:
sudo tee /etc/ssh/sshd_config.d/99-hardening.conf >/dev/null <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
MaxAuthTries 3
EOF
sudo sshd -t && sudo systemctl reload ssh
Keep your current session open and test a new login in another window. If it fails, you can still fix the file.
3. Firewall
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable
The Java server ports (8080, 8443, 9990, 4848) are not in this list on purpose.
4. Bind Java and databases to localhost
The firewall is the second line of defence. The first is not listening on public addresses at all. Check:
sudo ss -ltnp
Every line except SSH, Nginx on 80 and 443 should show 127.0.0.1 or ::1. Fix any that show 0.0.0.0 or *: Tomcat connector address="127.0.0.1", Spring Boot server.address=127.0.0.1, WildFly -b 127.0.0.1 -bmanagement 127.0.0.1, databases bind-address. Docker published ports bypass UFW, so publish them as 127.0.0.1:8080:8080.
5. Fail2ban
sudo apt install -y fail2ban
sudo tee /etc/fail2ban/jail.d/sshd.local >/dev/null <<'EOF'
[sshd]
enabled = true
maxretry = 4
findtime = 10m
bantime = 1h
EOF
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
6. Automatic security updates
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
systemctl status unattended-upgrades --no-pager
This installs security patches for the operating system. It does not update your JDK major version, application server or your own application. Those need a routine of their own: a look once a month is enough. Kernel updates need a reboot to take effect; schedule it.
Also worth doing
- Remove the sample applications and the manager from Tomcat. Never leave admin consoles on a public address. Use an SSH tunnel:
ssh -L 9990:127.0.0.1:9990 deploy@server. - Keep secrets in an
EnvironmentFilewith mode 640, not in the JAR or in Git. - Hide the server version in Nginx:
server_tokens off;. - Run the application as its own unprivileged user, never as root. Add
NoNewPrivileges=trueandProtectSystem=fullto the systemd unit. - Take backups, and test restoring one: backup guide.
On managed Java hosting we apply updates and watch the server for you.
Last updated October 29, 2026.