Automated setup for a secure VPS with WireGuard VPN, Traefik reverse proxy, and Portainer container management.
- WireGuard VPN: Secure private network access to administration services.
- Traefik v2: Reverse proxy with automatic HTTPS via Let's Encrypt.
- Portainer: Web-based interface for Docker container management.
- Website Deployment: Easy deployment of Apache-based websites with automatic SSL.
- Minecraft Servers: Deploy and manage multiple Minecraft servers (Vanilla, Forge, Fabric, NeoForge, Paper).
- Security: Admin services accessible only through the VPN and optional IP whitelisting.
- Automation: Complete setup in a few commands.
- Modularity: Add or remove services easily.
vps-config/
├── init.sh # Main initialization script (run as root)
├── scripts/ # User scripts directory
│ ├── setup.sh # Admin stack setup
│ ├── start-admin-stack.sh
│ ├── show-vpn-config.sh
│ ├── update-ufw.sh # UFW firewall management
│ ├── new-site.sh # Deploy new websites
│ └── new-minecraft.sh # Deploy new Minecraft servers
├── .env # Environment configuration
└── README.md # This file
All scripts in the scripts/ directory are automatically copied to /home/$USER_NAME/ during initialization.
- A fresh Debian or Ubuntu VPS.
- Root access to the server.
- A domain name pointing to the VPS.
- Basic knowledge of Linux and Docker.
git clone https://github.com/AlainPiallat/vps-config.git
cd vps-configcp .env.example .env
nano .envMain variables:
USER_NAME: Linux username to create.USER_EMAIL: Email used for Let's Encrypt.DOMAIN: Main domain name.VPN_SUBNET: VPN network (default: 10.13.13.0/24).SSH_PUBLIC_KEY: Your SSH public key.
chmod +x init.sh # Make script executable
sudo ./init.shThis will:
- Create the
/etc/vps-configdirectory for shared configuration files. - Create the user account.
- Install Docker and Docker Compose.
- Set up WireGuard VPN.
- Configure UFW and Fail2Ban.
- Harden SSH access.
- Generate the port registry file (
/etc/vps-config/.port). - Copy configuration files to
/etc/vps-config.
The script is idempotent and can be re-run safely.
su USER_NAME
cd ~
./setup.shThis will:
- Create the
~/admin-stackdirectory. - Generate the Traefik configuration.
- Create
docker-compose.yml. - Prepare startup scripts.
cd ~/admin-stack
./start.shOnce init.sh is complete, WireGuard configurations will be generated for your devices.
Display or export configurations with:
./show-vpn-config.shTo connect:
- Install WireGuard on your device.
- Import the configuration file.
- Enable the VPN tunnel.
Once connected to the VPN:
- Traefik Dashboard:
http://10.13.13.1:8080/dashboard/ - Portainer:
http://10.13.13.1:9000/
Note: Create your Portainer admin account within the first 5 minutes after startup.
- Admin interfaces accessible only via VPN.
- Automatic HTTPS certificates via Let's Encrypt.
- Fail2Ban protection against brute-force attacks.
- UFW firewall allowing only necessary ports.
- SSH hardened with key-only authentication.
The project creates a dedicated directory /etc/vps-config to store shared configuration files:
/etc/vps-config/
├── .env # Environment variables (copy from project)
├── .port # Port registry file
└── update-ufw.sh # UFW update script
The .port file is automatically generated during initialization and lists all open ports in the following format:
<port> <protocol> <service_name>
Example:
22 tcp SSH
80 tcp HTTP
443 tcp HTTPS
51820 udp WireGuard
Usage:
- This file is used by the
update-ufw.shscript to manage firewall rules. - To add a new port, edit
/etc/vps-config/.portand runsudo /etc/vps-config/update-ufw.sh. - Comments can be added with
#at the beginning of a line. - Service names with spaces are supported (e.g.,
8080 tcp Web Application).
To update UFW rules based on the port registry:
# Edit the port configuration
sudo nano /etc/vps-config/.port
# Apply the changes
sudo /etc/vps-config/update-ufw.shThis will reset UFW and recreate all rules based on the .port file.
Internet
│
├─── Ports 80/443 ───→ Traefik ───→ Websites (auto HTTPS)
│
├─── Ports 25565+ ──→ Minecraft servers (one port per server)
│ (DNS SRV records map subdomains to each port)
│
└─── Port 51820 ───→ WireGuard VPN
│
└─→ 10.13.13.0/24 (VPN Network)
├─→ Traefik Dashboard (:8080)
├─→ Portainer (:9000)
└─→ Minecraft RCON (VPN only)
All containers share an external Docker network named
proxy, enabling secure internal routing through Traefik.
This project evolves over time. Here is how to safely pull new scripts and use new features without affecting running services.
# On your local machine, in the cloned repository
git pull
# Review what changed
git log --oneline -10
git diff HEAD~1 HEADNew or updated scripts need to be copied to the server. Run init.sh again - it is fully idempotent and will only update what needs updating (it will not reinstall Docker, recreate users, or regenerate WireGuard keys if they already exist):
# On the server, as root
sudo ./init.shThis re-copies all scripts from scripts/ to /home/$USER_NAME/ and refreshes /etc/vps-config/.env.
Alternatively, copy only the scripts you need manually:
scp scripts/new-minecraft.sh user@your-vps:~/new-minecraft.sh
chmod +x ~/new-minecraft.sh| Component | Impact of re-running init.sh |
|---|---|
Running websites (/srv/site-*) |
None - containers are untouched |
Running Minecraft servers (/srv/mc-*) |
None - containers are untouched |
Admin stack (~/admin-stack) |
None - re-run setup.sh only if Traefik config changed |
| WireGuard VPN | None - keys and config are preserved |
| UFW firewall | Refreshed from /etc/vps-config/.port (no port is lost) |
| SSH config | Preserved - original backed up with timestamp |
If new variables are added to .env.example (such as MINECRAFT_PORT_START):
- Compare
.env.examplewith your.env:diff .env.example .env
- Add missing variables to your
.env - Re-run
init.shto propagate the file to the server
If setup.sh was updated (e.g. new Traefik config):
# As regular user on the server
./setup.sh # regenerates ~/admin-stack config files
cd ~/admin-stack
docker compose pull # pull latest images
docker compose up -d # apply new config (zero-downtime for running sites)Running websites and Minecraft servers are unaffected because they run in separate containers on the shared proxy network.
sudo systemctl status wg-quick@wg0
sudo journalctl -u wg-quick@wg0 -n 50ping 10.13.13.1
docker ps
docker compose logs traefik# Start admin stack
cd ~/admin-stack && ./start.sh
# View logs
docker compose logs -f
# Restart stack
docker compose restart
# Stop stack
docker compose down
# Check VPN status
sudo wg showDeploy a new website with automatic SSL certificate:
./new-site.sh [subdomain]Examples:
./new-site.sh blog # Creates blog.yourdomain.com
./new-site.sh @ # Creates yourdomain.comThe new-site.sh script automatically:
- Creates a directory structure in
/srv/site-<name>/ - Generates a Docker Compose configuration with Apache
- Creates a welcome HTML page
- Configures Traefik for automatic HTTPS (Let's Encrypt)
- Starts the container
- Verifies the site is accessible
Note: Sites are stored in /srv/ following Linux Filesystem Hierarchy Standard (FHS).
/srv/site-blog/
├── docker-compose.yml # Docker configuration
├── Dockerfile # Custom PHP+Apache image
├── manage.sh # Management script
├── html/ # Website files (edit here)
│ └── index.html # Main page
├── logs/ # Apache access and error logs
└── README.md # Site-specific documentation
Each site includes a manage.sh script for common operations:
cd /srv/site-blog
# Start the site
./manage.sh start
# Stop the site
./manage.sh stop
# Restart the site
./manage.sh restart
# View logs
./manage.sh logs
# Check status
./manage.sh status- Edit files in
/srv/site-<name>/html/ - Restart the container:
cd /srv/site-<name> && ./manage.sh restart
- DNS record for the (sub)domain must point to your VPS IP
- Ports 80 and 443 must be open (configured during init.sh)
- Traefik must be running (admin stack)
- Apache containers use port 80 internally, Traefik handles external routing
Deploy a new Minecraft server with an interactive configuration wizard:
./new-minecraft.sh <folder-name>
./new-minecraft.sh @ # default server (folder: mc, url: mc.example.com)Examples:
./new-minecraft.sh survival # /srv/mc-survival, default subdomain: survival
./new-minecraft.sh creative # /srv/mc-creative, default subdomain: creative
./new-minecraft.sh @ # /srv/mc-mc, default subdomain: mcThe new-minecraft.sh script:
- Asks a series of questions (subdomain, server type, version, RAM, MOTD, etc.)
- Finds the next available port starting from
MINECRAFT_PORT_START(default: 25565) - Creates a directory structure in
/srv/mc-<name>/ - Generates a
docker-compose.ymlusingitzg/minecraft-server - Registers the port in
/etc/vps-config/.portand reloads UFW - Starts the server
- Displays the DNS SRV record to create at your registrar
| Type | Description |
|---|---|
VANILLA |
Official Mojang server, no mods |
PAPER |
Performance-optimized, supports Bukkit/Spigot plugins |
FABRIC |
Lightweight modern mod loader |
FORGE |
Classic mod loader, widest mod ecosystem |
NEOFORGE |
Modern fork of Forge |
Each server runs on a unique port. A DNS SRV record maps a subdomain to that port so players never need to type the port number.
For a server on port 25566, create this record at your DNS provider:
Type: SRV
Name: _minecraft._tcp.survival
Target: yourdomain.com.
Port: 25566
Priority: 0
Weight: 5
TTL: 3600
Players then connect simply with survival.yourdomain.com.
/srv/mc-survival/
├── docker-compose.yml # Docker configuration (auto-generated)
├── manage.sh # Management script
├── server.env # Runtime variables and RCON password (chmod 600)
├── data/ # World, configs, server.properties (persistent)
└── mods/ # .jar mod files (Forge / Fabric / NeoForge only)
Each server includes a manage.sh script:
cd /srv/mc-survival
./manage.sh start # Start the server
./manage.sh stop # Warn players (10s) then stop
./manage.sh restart # Restart the server
./manage.sh rebuild # Pull latest image and restart
./manage.sh logs # Follow server logs (Ctrl+C to exit)
./manage.sh status # Show container status and info
./manage.sh console # Open interactive RCON console
./manage.sh backup # Backup world data to .tar.gz
./manage.sh backup my-save.tar.gz
./manage.sh whitelist add <player>
./manage.sh whitelist remove <player>
./manage.sh whitelist list
./manage.sh op <player> # Give operator permissions- Drop
.jarmod files into/srv/mc-<name>/mods/ - Rebuild the server:
cd /srv/mc-<name> && ./manage.sh rebuild
During setup, the script asks whether to allow offline (cracked) clients:
- Online mode ON (default): players must have a valid Mojang/Microsoft account. Skins and UUIDs are authenticated by Mojang servers.
- Online mode OFF: anyone can connect without an official account. This is useful for private servers where some participants do not own the game, but it disables Mojang authentication entirely.
This can be changed later by editing ONLINE_MODE in the server's docker-compose.yml and running ./manage.sh rebuild.
You can deploy your own applications behind Traefik by connecting them to the proxy network and adding the required Traefik labels.
Example:
services:
myapp:
image: nginx:alpine
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`myapp.yourdomain.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
networks:
proxy:
external: trueAfter initialization, the following scripts are available in your home directory:
| Script | Description |
|---|---|
setup.sh |
Set up the admin stack (Traefik + Portainer) |
start-admin-stack.sh |
Start the admin stack containers |
show-vpn-config.sh |
Display WireGuard VPN configurations |
new-site.sh |
Deploy a new Apache website with automatic SSL |
new-minecraft.sh |
Deploy a new Minecraft server (interactive setup) |
update-ufw.sh |
Update firewall rules (requires sudo) |
- Scripts and utilities for day-to-day operations
.env- Environment variables.port- Port registry for firewall managementupdate-ufw.sh- Firewall management script
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
This configuration is provided as-is. You are responsible for reviewing and adapting it to your specific use case.
- Based on the work of thienhaole92.
- Thanks to the Traefik and WireGuard communities.
Alain Piallat - GitHub