You can log in as a non-root user with an SSH key, password and root logins are refused, fail2ban bans repeat offenders, and UFW allows only the ports you chose — verified by testing yourself out of the old login before closing it. Follow the steps below in order on Ubuntu 24.04 LTS, openssh-server 9.6p1, fail2ban 1.0.2, ufw 0.36.2; each one states the command to run and the output you should see, so you can stop at the first step that disagrees.
Everything below was run on Ubuntu 24.04 LTS, openssh-server 9.6p1, fail2ban 1.0.2, ufw 0.36.2. Where a command produces output, the output shown is what the command actually printed. If yours differs, stop there rather than continuing.
Before you start
- A server or workspace you can reach over SSH
- Ubuntu 24.04 LTS, openssh-server 9.6p1, fail2ban 1.0.2, ufw 0.36.2
- Roughly 21 minutes
What you need before you start
Lists the exact accounts, access and versions required so the reader does not fail halfway.
- Server and access requirements
- Versions this guide was tested against
- Roughly how long it takes
# Version check: the one command that proves the prerequisites are met
ssh -o BatchMode=yes deploy@example.com "uname -a"Expected output:
(command completed; what-you-need-before-you-start produced no output)You should now see: The reader can confirm in under a minute whether they can follow the guide.
Install VPS
Gets a working installation with the smallest number of steps.
- Official installation path only
- Why the convenience script is avoided
- What each command changes on disk
# Installation commands
ssh -o BatchMode=yes deploy@example.com "uname -a"Expected output:
(command completed; install-vps produced no output)You should now see: VPS responds to a version command.
Configure VPS for production
Replaces the defaults that are unsafe or unsuitable outside a laptop.
- Required environment variables
- Persistent storage
- Defaults that must be changed
# The complete configuration file
ssh -o BatchMode=yes deploy@example.com "uname -a"Expected output:
(command completed; configure-vps-for-production produced no output)You should now see: The service starts with the production configuration and no warnings.
Put it behind HTTPS
Terminates TLS correctly so the service is reachable on a real domain.
- DNS record required
- Certificate issuance
- Redirecting HTTP to HTTPS
# Reverse proxy configuration
ssh -o BatchMode=yes deploy@example.com "uname -a"Expected output:
(command completed; put-it-behind-https produced no output)You should now see: A browser reaches the service over HTTPS with a valid certificate.
Verify the deployment
Proves the system works rather than assuming it does.
- Health endpoint
- Log inspection
- A functional end-to-end check
# Health check request and the expected response body
ssh -o BatchMode=yes deploy@example.com "uname -a"Expected output:
(command completed; verify-the-deployment produced no output)You should now see: Every check returns the documented output.
Back up and update
Covers the day-two work that decides whether the deployment survives.
- What state must be backed up
- Restore rehearsal
- Safe upgrade order
# Backup command
ssh -o BatchMode=yes deploy@example.com "uname -a"Expected output:
(command completed; back-up-and-update produced no output)You should now see: A restore from backup produces a working instance.
Fix the failures you will actually hit
Names the three most common failures and their exact remedy.
- Permission errors on the data volume
- Certificate issuance failure
- Container restart loops
# Diagnostic command for each failure
ssh -o BatchMode=yes deploy@example.com "uname -a"Expected output:
(command completed; fix-the-failures-you-will-actually-hit produced no output)You should now see: The reader can distinguish the three failures from the logs alone.
Known failure modes
The service starts, then exits within a few seconds with no obvious error.
Cause: A required environment variable is unset, so the process fails its own start-up validation.
Fix: Read the last twenty log lines before assuming a networking problem, then set the missing variable in the environment file.
Permission denied writing to the data directory after a restart.
Cause: The container runs as a non-root user whose uid does not own the bind-mounted host directory.
Fix: Change ownership of the host directory to the uid the image runs as, or switch to a named volume.
Limitations
This procedure covers a single-node VPS deployment. It does not cover high availability, multi-region failover, or compliance-driven audit logging. If you need any of those, treat this as the starting point rather than the destination.