From zero to production-ready in one command.
Automated Ubuntu VPS provisioning script that sets up your server with user management, SSH hardening, Docker, reverse proxy, and more — all executed remotely over SSH from your local machine.
./vps-setup.sh --host=192.168.1.100 --use-password=yes --identity=~/.ssh/id_rsa --target-user=deploy --yes| Feature | Description |
|---|---|
| User Management | Creates a new user with sudo privileges |
| SSH Hardening | Key-based auth, disables password login |
| Docker + Compose | Installs Docker Engine and Docker Compose v2 |
| Reverse Proxy | Deploys nginx-proxy with automatic HTTPS |
| Firewall | Configures UFW rules (if active) |
| Shell Customization | Docker aliases, auto-cd to projects folder |
| Idempotent | Safe to run multiple times — skips what's already done |
- Bash shell
- SSH client
- sshpass (only if using password authentication)
# Ubuntu/Debian sudo apt-get install -y sshpass # macOS brew install hudochenkov/sshpass/sshpass
- Fresh Ubuntu server (20.04, 22.04, 24.04 or newer)
- Root access or a user with sudo privileges
- SSH access enabled
git clone https://github.com/dersonsena/vps-setup.git
cd vps-setup
chmod +x vps-setup.shInteractive mode — the script asks you everything:
./vps-setup.shCLI mode — pass flags and only type passwords:
./vps-setup.sh --host=10.0.0.1 --use-password=yes --target-user=deploy --yesAt the end, you'll receive:
- Generated username and password (if applicable)
- Server SSH keys (for backup)
- Access URLs for your server
| Flag | Description | Default |
|---|---|---|
--host=<host> |
Remote host/IP (required) | — |
--user=<user> |
Remote SSH user | root |
--port=<port> |
Remote SSH port | 22 |
--use-password=yes|no |
Use password authentication | (prompted) |
--identity=<path> |
Path to SSH private key | ~/.ssh/id_rsa |
--target-user=<user> |
Username to create on server | dev |
--pubkey=<path> |
Path to SSH public key file | ~/.ssh/id_rsa.pub |
--projects-dir=<dir> |
Projects directory on server | /projects |
--yes, -y |
Skip confirmation prompt | — |
--help, -h |
Show help message | — |
Security: Passwords are never accepted via CLI — they are always prompted interactively.
CLI mode: When any flag is provided, fields with defaults are auto-filled without prompting. Only passwords require interaction.
The script supports three authentication modes to handle different server configurations:
For servers that accept password authentication:
./vps-setup.sh --host=10.0.0.1 --use-password=yes --yes
# Prompts: SSH passwordFor servers that only accept public key authentication:
./vps-setup.sh --host=10.0.0.1 --use-password=no --identity=~/.ssh/id_rsa --yes
# No password promptsFor servers that require public key and password (two-factor):
./vps-setup.sh --host=10.0.0.1 --use-password=yes --identity=~/.ssh/id_rsa --yes
# Prompts: SSH password (used after key auth succeeds)How to know which mode? If your manual
ssh user@hostasks for a password after key auth, your server uses MFA. The script's debug output will showAuthenticated using "publickey" with partial successin this case.
When connecting as a non-root user (e.g., --user=deploy), the script needs sudo access for provisioning. It will:
- Reuse the SSH password as sudo password (if available)
- Or prompt separately for the sudo password
./vps-setup.sh --host=10.0.0.1 --user=deploy --use-password=yes --identity=~/.ssh/id_rsa --yes
# Prompts: SSH password (also used for sudo)./vps-setup.shEverything is prompted. Great for first-time use.
./vps-setup.sh \
--host=203.0.113.50 \
--use-password=no \
--identity=~/.ssh/id_ed25519 \
--target-user=app \
--pubkey=~/.ssh/id_ed25519.pub \
--yesConnects with SSH key, creates user app with auto-generated password, uses ed25519 keys.
./vps-setup.sh \
--host=10.100.0.5 \
--user=admin \
--port=2222 \
--use-password=yes \
--identity=~/.ssh/corporate_key \
--target-user=deployer \
--projects-dir=/opt/services \
--yesConnects on custom port with MFA (key + password), creates deployer user, uses /opt/services as projects dir.
./vps-setup.sh --host=203.0.113.50 --use-password=no --identity=~/.ssh/id_rsa --yesSafely skips everything that's already configured:
✅ User 'dev' already exists.
✅ ~/.ssh for root already exists.
✅ Client public key already exists in root's authorized_keys. Skipping.
✅ ~/.ssh for 'dev' already exists.
✅ Client public key already exists in 'dev' authorized_keys. Skipping.
✅ Server SSH key with comment 'server@domain.com' already exists. Skipping key generation.
✅ Alias 'dc' already exists in /home/dev/.bash_aliases
✅ Alias 'dps' already exists in /home/dev/.bash_aliases
✅ Alias 'dstop' already exists in /home/dev/.bash_aliases
✅ Alias 'drm' already exists in /home/dev/.bash_aliases
✅ Alias 'lsa' already exists in /home/dev/.bash_aliases
✅ Projects directory already exists (/projects)
⚠️ Reverse proxy already installed and configured (docker network 'reverse-proxy' found). Skipping.
- Updates all packages to latest versions
- Installs essential tools:
curl,git,ca-certificates,lsof,ufw
- Creates a new user (or detects existing one)
- Adds user to sudo group for administrative tasks
- Adds both root and target user to docker group
- Adds your public key to
authorized_keysfor root and target user (only if not already present) - Disables password authentication (SSH keys only)
- Keeps root login enabled (accessible via SSH key)
- Generates a server ed25519 SSH key pair (skips if key with comment
server@domain.comalready exists) - Creates timestamped backup of
sshd_configbefore modifications
- Installs Docker Engine and Docker Compose v2
- Creates a
reverse-proxyDocker network - Stops conflicting web servers (apache2, nginx, caddy, traefik)
- Frees ports 80 and 443
- Clones Dev-Toolbelt/reverse-proxy
- Starts nginx-proxy for automatic container routing
- Enables HTTP (80) and HTTPS (443) access
- Skipped entirely if docker network
reverse-proxyalready exists
Aliases added to ~/.bash_aliases (each checked individually, no duplicates):
| Alias | Command |
|---|---|
dc |
docker compose |
dps |
docker ps with formatted table output |
dstop |
Stop all running containers |
drm |
Remove all containers |
lsa |
ls -lah |
Auto-cd to projects directory is configured on SSH login for both root and target user.
- Allows SSH (port 22)
- Allows HTTP (port 80)
- Allows HTTPS (port 443)
UFW is only configured if already active. The script never force-enables the firewall.
╔════════════════════════════════════════════════════════════╗
║ ║
║ ✅ VPS Setup Completed Successfully! ║
║ ║
╚════════════════════════════════════════════════════════════╝
Username: deploy
Password: kR4mN7xQp2wT5vB
🌐 Your server is now accessible at:
• http://203.0.113.50
• https://203.0.113.50
# As the created user
ssh deploy@203.0.113.50
# As root
ssh root@203.0.113.50You'll land directly in the projects directory thanks to auto-cd.
Create a docker-compose.yml with reverse proxy labels:
services:
web:
image: nginx:alpine
environment:
- VIRTUAL_HOST=your-domain.com
- LETSENCRYPT_HOST=your-domain.com
- LETSENCRYPT_EMAIL=you@example.com
networks:
- reverse-proxy
networks:
reverse-proxy:
external: truedocker compose up -dThe reverse proxy automatically detects your container and routes traffic to it.
| Feature | Detail |
|---|---|
| No passwords in CLI | Passwords are always typed interactively, never in command arguments |
| Key-only SSH | Password authentication is disabled after setup |
| MFA support | Works with servers requiring key + password |
| Strong passwords | Auto-generated 15-char passwords (A-Za-z0-9@#%+=_) |
| Safe transmission | Sensitive data is base64-encoded through SSH |
| Temp file security | Password files use mode 600 and are cleaned up on exit |
The script shows detailed debug output on failure. Look for these patterns:
| Debug message | Meaning | Solution |
|---|---|---|
Authentications that can continue: publickey |
Server only accepts keys | Use --use-password=no --identity=<key> |
Authenticated using "publickey" with partial success |
Server requires MFA | Use --use-password=yes --identity=<key> |
Permission denied (password) |
Wrong password | Check your password |
Permission denied (publickey) |
Wrong key or no key | Check --identity path |
newgrp docker
# or logout and login againsudo ufw status
sudo ufw allow 80/tcp
sudo ufw allow 443/tcpssh -i ~/.ssh/your_key user@server-ipdocker compose ps
docker logs reverse-proxyWhen connecting as a non-root user, the script prompts for the sudo password. If you provided an SSH password, it's reused automatically.
Contributions are welcome! Feel free to open an issue or submit a pull request.
This project is open source and available under the MIT License.
Kilderson Sena
- GitHub: @dersonsena
Note: This script makes significant changes to your server's SSH and firewall configuration. Always test on a development server first and ensure you have console access to your VPS in case something goes wrong.