Turn a brand-new Ubuntu VPS into a secure, fast, production-ready server in minutes. An interactive terminal installer for people who don't want to learn 40 hardening guides first.
sudo apt-get update -qq && sudo apt-get install -y curl ca-certificates && sudo curl -fsSL https://raw.githubusercontent.com/mh-sudo/vps-forge/main/install.sh | sudo bash
Vps Forge is a free, open-source, pure-Bash terminal UI that provisions a fresh Ubuntu 22.04 or 24.04 VPS to a production-ready state. It creates a safe admin user, hardens SSH, sets up a firewall that actually works with Docker, installs Fail2ban and automatic security updates, tunes the kernel and memory, installs Docker, and can deploy a self-hosting panel like Coolify or CloudPanel.
You pick a profile, review exactly what will change, confirm, and it does the rest. Every change is backed up first, and anything that could lock you out has an automatic rollback timer.
It works on any provider that gives you Ubuntu and root: Hetzner, DigitalOcean, Vultr, Linode, OVH, Contabo, AWS Lightsail, and the rest.
- You just bought a VPS to host an app, a bot, an API, an AI agent, or a side project, and you don't know what to do after the first login.
- You've been told to "secure your server" and don't know where to start.
- You use Docker and want published ports to be private by default.
- You're comfortable on the command line and are tired of pasting the same 200 lines into every new box.
Most "how to secure a VPS" guides fail beginners in the same few places.
| Common failure | What Vps Forge does |
|---|---|
| Changing the SSH port or disabling passwords and getting locked out | Validates with sshd -t, applies changes behind a timed auto-revert, and makes you confirm from a new session before the timer is cancelled |
| Turning on UFW before allowing SSH | Refuses. The active SSH port is allowed first, always |
| Disabling password login with no working key | Refuses until a key for the target user is verified |
| Docker ports ignoring UFW, so "denied" ports are open to the internet | Uses the DOCKER-USER chain so published container ports are closed by default until you allow them |
| Breaking something and not knowing what changed | Every touched file is backed up to /var/backups/vps-forge/<timestamp>.<tag>/, and vps-forge rollback restores it |
| Not knowing if it worked | Runs a Lynis audit before and after and shows the score difference |
Run this on a fresh server as root, or with sudo — one line, copy-paste:
sudo apt-get update -qq && sudo apt-get install -y curl ca-certificates && sudo curl -fsSL https://raw.githubusercontent.com/mh-sudo/vps-forge/main/install.sh | sudo bashWhy not just curl ... | sudo bash? Because Ubuntu Server does not come
with curl preinstalled (Desktop does; minimal server/cloud images don't).
The first part of that line installs it in a couple of seconds. If your box
already has curl (or has wget — the installer accepts either), the short form
works:
curl -fsSL https://raw.githubusercontent.com/mh-sudo/vps-forge/main/install.sh | sudo bashThe entrypoint is tiny. It downloads the files, verifies every checksum, shows live download progress, and starts the guided setup.
Prefer to read it first? Good habit.
git clone https://github.com/mh-sudo/vps-forge.git
cd vps-forge
less install.sh # the whole entrypoint
sudo ./vps-forgeKeep a second SSH session open the entire time. Vps Forge will remind you, but it's the best safety net you have.
- Preflight. OS, RAM, disk, virtualization, current SSH user and port, detected provider, and the existing firewall state.
- Profile. Minimal, Recommended, Docker-Host, or Custom with a checklist. Each item has a one-line description and a risk tag.
- Review. A dry-run diff of exactly what will change, then an explicit confirm.
- Apply. Live progress per module with ✓ / ✗ / skipped.
- Report. A summary saved to
/root/vps-forge-report.txt, the Lynis score delta, and a reboot recommendation only if one is needed.
It's built for an 80-column terminal, handles Ctrl+C safely, and uses one accent color. No ASCII-art banner.
| Profile | Good for | Includes |
|---|---|---|
| Minimal | Anyone, first run | Base tooling, SSH hardening, UFW, unattended security upgrades |
| Recommended | Most single-app servers | Minimal, plus sysctl hardening, swap, chrony time sync, BBR, journald limits, admin user, Fail2ban, PAM hardening, auditd, AppArmor, AIDE, rkhunter, health checks |
| Docker-Host | Containers and self-hosting | Recommended, plus Docker Engine and Compose from Docker's official apt repo, a hardened daemon.json, and the Docker-safe firewall |
| Custom | You | Pick any modules from the checklist |
Security hardening
- New sudo admin user with an ed25519 SSH key, optional root login disable
sshd_config.ddrop-in (your main config is never edited) with modern ciphers, KEX and MACs,MaxAuthTries,LoginGraceTime,AllowUsers, and a custom port option- UFW with default-deny incoming, rate-limited SSH, and consistent IPv6 handling
- Fail2ban with an sshd jail (CrowdSec may be added as an alternative module)
- Unattended security upgrades with a reboot policy
- Kernel and sysctl hardening that doesn't break Docker or networking
- PAM faillock, password policy, sane umask, unused services and filesystems disabled
- AppArmor enforcing, auditd baseline rules, AIDE file integrity monitoring, rkhunter and Lynis
- Optional TOTP 2FA for SSH with a lockout-safe flow and a backup-codes warning
Server optimization
- Timezone, locale, chrony NTP, hostname
- Swap file sized to your RAM,
vm.swappiness, optional zram - File descriptor limits, TCP BBR, journald size limits, logrotate
- Optional
/tmpon tmpfs, disk TRIM - Base tools: curl, git, htop, ncdu, jq and friends
Production readiness
- Docker Engine and Compose plugin (official apt repo, never the snap)
- Caddy or Nginx reverse proxy with automatic TLS
- node_exporter for Prometheus (loopback-only by default), a health script, and disk/RAM alerts by email or webhook
- Scheduled restic backups to S3, SFTP, REST or a local target, with a built-in restore test
- Tailscale, WireGuard, or Cloudflare Tunnel for private admin access
- Deploy user, Node.js and Python runtimes
One-click panels (pick one)
- Coolify, CloudPanel, CyberPanel
- Requirements are pre-checked (OS, RAM, ports, conflicts with Docker, UFW, Nginx) and the install is blocked with a clear message if they aren't met
- Only the ports the panel needs get opened, an admin password is generated and shown once, and the final access URL is printed
[!WARNING] CyberPanel has a history of serious security vulnerabilities, and real servers have been compromised through them. If you pick it, Vps Forge shows a visible warning and an extra hardening checklist. If you have a choice, consider Coolify or CloudPanel instead.
This is the one that quietly bites people.
When you publish a container port (-p 8080:80), Docker adds its own iptables rules that run before UFW's. So ufw deny 8080 does nothing, and your "private" database or admin tool is open to the whole internet. Docker's own documentation describes this behavior.
Vps Forge handles it with the supported approach: custom filtering in the DOCKER-USER chain, which Docker evaluates before its own rules.
- Published container ports are not reachable from outside by default
- Established traffic and traffic between containers on Docker networks keep working
daemon.jsonsets the default published-port bind address to127.0.0.1, keeps Docker's iptables management on, and survives Docker restarts and reboots- IPv6 is covered, and rules persist across reboots
Open a port on purpose:
vps-forge docker-allow 8080/tcp
vps-forge docker-allow 5432/tcp my-postgres-containerDon't take our word for it, prove it:
vps-forge verify-firewallThis starts a throwaway container with a published port, shows from the outside that it's blocked, opens it, shows it's reachable, and cleans up after itself.
The reasoning behind the chosen strategy, and the alternatives we compared, are in DECISIONS.md.
sudo ./vps-forge # interactive TUI
sudo ./vps-forge --dry-run # show what would change, touch nothing
sudo ./vps-forge --verbose # detailed output
sudo ./vps-forge --help
vps-forge rollback # restore the most recent backup set
vps-forge rollback <snapshot> --force # scripted rollback, skips prompts
vps-forge docker-allow <port>/<proto> [comment] # expose a published port through DOCKER-USER
vps-forge docker-deny <port>/<proto> # close it again
vps-forge docker-fw-status # show the DOCKER-USER rules
vps-forge verify-firewall # prove Docker ports are private by default
vps-forge backup-test # prove backups restore
vps-forge panel-remove coolify|cloudpanel|cyberpanel # uninstall a panel (asks to type the name)
vps-forge self-clean [--scope=all|panels|docker|hardening] [--yes] # remove everything vps-forge applied
vps-forge status # snapshots, guards, applied modules
vps-forge guard-cancel sshd|ufw|totp # cancel an armed auto-revert guard (from a VERIFIED new session)Notes: self-clean is destructive and asks you to type self-clean to confirm;
it only deletes users, packages and Docker data that vps-forge itself created
(pre-existing sites, databases and firewall rules are left alone).
panel-remove only touches the panel's own packages and configs — the web
stack and site data stay.
For automation, cloud-init, or when there's no TTY:
sudo ./vps-forge --yes --profile=recommended --config=forge.yaml
# pick specific modules instead of a profile:
sudo ./vps-forge --yes --module=sshd,ufw,fail2ban,docker,docker_fw --config=forge.yaml# forge.yaml (flat "key: value" config; see examples/test-config.yaml)
sys.timezone: UTC
admin.username: deploy
admin.pubkey: ssh-ed25519 AAAA... you@example.com
admin.disable_root_login: false
ssh.port: 2222
fw.open_ports: 80,443
docker.bind_ip: 127.0.0.1
backup.target: none
panel: noneEvery key below can be set in a --config file; anything you don't set gets a
safe default (the full list with defaults lives in lib/common.sh →
cfg_load_defaults). Selected keys:
| Key | Default | Meaning |
|---|---|---|
sys.timezone |
(current) | tz name, e.g. Asia/Dhaka; empty/auto keeps current |
sys.hostname |
keep |
set a new hostname; keep leaves it |
admin.username |
none |
admin user to create (skipped when none) |
admin.pubkey |
(empty) | SSH public key for the admin user; placeholders are refused |
admin.disable_root_login |
false |
root login disabled once the admin key is verified |
ssh.port |
keep |
change the SSH port (dual-listen + verify flow) |
ssh.password_auth |
keep-until-verified |
no only after a key is verified |
ssh.allow_tcp_forwarding |
false |
CIS default; set true if you tunnel |
fw.open_ports |
(empty) | extra UFW ports, e.g. 80,443 |
swap.size |
auto |
MB, or auto (RAM-based sizing) |
docker.bind_ip |
127.0.0.1 |
default bind for -p publishes (0.0.0.0 = public) |
proxy.engine / proxy.domain / proxy.upstream_port |
none |
Caddy reverse proxy settings |
backup.target / backup.paths / backup.schedule |
none |
restic backup settings |
tailscale.auth_key |
(empty) | join a tailnet non-interactively |
wg.mode |
access |
access (server only) or full (full tunnel + NAT) |
panel / panel_cyberpanel.accept_risk |
none / false |
panel choice; CyberPanel needs explicit risk acceptance |
totp.enabled / totp.users / totp.confirmed |
false |
SSH TOTP 2FA for the listed users only |
guard.timeout |
180 |
auto-revert window (seconds) for sshd/ufw/TOTP guards |
- Pure Bash.
set -Eeuo pipefail, an ERR trap, ShellCheck-clean, no runtime to install. - TUI. Uses
gum(auto-installed and checksum-verified), withwhiptailas a fallback. - Modular.
lib/holds shared helpers,modules/holds one self-contained file per feature, and a single manifest registers them. - Idempotent. Every module is safe to re-run.
- Logged. Structured logs go to
/var/log/vps-forge.log.
vps-forge/
├── install.sh # tiny verified entrypoint
├── vps-forge # main script
├── checksums.txt # sha256 for every shipped file
├── lib/ # config, TUI, safety layer (snapshots + guards), preflight, runner
├── modules/ # one file per feature
│ └── manifest.conf # the single place modules are registered
├── examples/ # non-interactive config example
├── DECISIONS.md # design rationale + research citations
├── TESTING.md # the full test log
└── README.md
- Create
modules/my-feature.shwith the standard module functions (mod_<id>_plan/_check/_run, optional_ask). - Register it in
modules/manifest.conf. - Open a PR. See CONTRIBUTING.md.
- Ubuntu 22.04 or 24.04 LTS, fresh install recommended
- Root, or a user with sudo
- Internet access, and
curlorwget(the Quick start line installs curl for you — stock Ubuntu Server ships without it) - About 1 GB RAM to run (preflight flags anything under 900 MB; panels need 2 GB+, and Vps Forge checks before installing)
Vps Forge helps defend against: automated SSH brute-forcing, exposed container ports, weak default SSH and kernel settings, unpatched packages, and misconfiguration lockouts.
It does not replace: application security, secrets management, a proper backup strategy you've tested, or monitoring someone actually looks at. A hardened server running a vulnerable app is still a vulnerable app.
Trust: you're running a script as root, so read it. The installer verifies checksums, and the source is short and modular on purpose.
How do I secure a new Ubuntu VPS? At minimum: create a non-root sudo user, use SSH keys and disable password login, enable a default-deny firewall, install Fail2ban, and turn on automatic security updates. Vps Forge's Minimal profile does all of that, with lockout protection.
Does ufw deny protect Docker containers?
No. Docker publishes ports through iptables rules that are processed before UFW's, so UFW rules don't block them. The supported fix is filtering in the DOCKER-USER chain, which Vps Forge configures for you.
Will Vps Forge lock me out of my server? It's designed not to. SSH and firewall changes are validated, applied with an automatic rollback timer, and only kept after you confirm from a new connection. It also refuses to disable password login without a verified key. Your provider's web console is the last-resort recovery path, and Vps Forge tells you how to reach it.
Can I undo what it did?
Yes. Every file it touches is backed up first, and vps-forge rollback restores them. vps-forge self-clean removes everything vps-forge applied.
Is it safe to run on a server that already has stuff on it?
A fresh VPS is the intended target. On an existing server, use --dry-run first and read the review screen carefully.
Does it work on Debian, AlmaLinux, or Rocky? Not yet. Ubuntu 22.04 and 24.04 only for now.
Which VPS providers work? Any that give you Ubuntu with root or sudo access.
Vps Forge or Ansible? Ansible is great if you manage many servers and want to write playbooks. Vps Forge is for one or a few servers, guided, with no setup and no YAML to learn first. They solve different problems.
Vps Forge or a hosting panel? Panels manage apps. Vps Forge secures and prepares the server underneath, and can install a panel for you afterward.
Can I use it in cloud-init or CI?
Yes, via --yes --profile=... --config=....
Vps Forge is beta. Live-tested end to end on Ubuntu 24.04 (2 vCPU / 2 GB KVM: module runs, full profiles, idempotency re-runs, lockout failsafes, Docker firewall proofs, all three panels, rollback and self-clean); 22.04 is supported in code but not yet live-tested. The full log is in TESTING.md. Please read the review screen before confirming, and open an issue if anything surprises you.
shellcheck -S warning vps-forge install.sh lib/*.sh modules/*.sh scripts/*.sh
shfmt -d -ln bash vps-forge install.sh lib/*.sh modules/*.sh scripts/*.sh
./scripts/check.sh # manifest <-> modules <-> install.sh <-> checksums.txtCI runs the same gates on every push. See CONTRIBUTING.md for module conventions and the test protocol.
Found a vulnerability? Please don't open a public issue. See SECURITY.md.
If Vps Forge saved you an afternoon (or a server), a ⭐ helps other beginners find it.