- TheVowelsOfX's Newsletter
- Posts
- 🔐 From Manual Mess to Fully Automated VPN Setup on AWS with Pritunl
🔐 From Manual Mess to Fully Automated VPN Setup on AWS with Pritunl
How one midnight outage led to a zero-touch VPN automation pipeline using EC2 and AWS Secrets Manager.
It’s 11:45 PM on a Friday. You're on-call. Suddenly, you get the dreaded message:
“VPN is down. Team can't access internal tools.”
You SSH into the EC2 instance, hoping it’s just a service restart. Instead, you find a half-configured Pritunl server… and no documentation. You dig through commands, try to remember the right MongoDB URI, run pritunl default-password, and pray nothing breaks. You finally get it working... at 12:20 AM. Weekend ruined. Again.
Sound familiar?
That exact frustration pushed us to automate everything — once and for all.
In this post, we’ll walk you through how we set up a zero-touch, fully automated Pritunl VPN server on AWS. One that:
Installs itself from scratch
Configures MongoDB only once
Handles restarts and reboots gracefully
Securely stores admin credentials in AWS Secrets Manager
And most importantly — lets us sleep in peace 😴
Why Automate Pritunl?
Pritunl is a powerful and flexible VPN solution — supporting OpenVPN and WireGuard. But its manual setup involves:
Adding GPG keys and apt repos
Installing MongoDB and Pritunl
Waiting for MongoDB to be ready
Configuring database URLs manually
Generating admin credentials
Storing those credentials somewhere safe
Miss a step, or reboot the machine? You might be back at square one.
Automation brings:
💯 Repeatability
🔄 Safe restarts
🔐 Secure credential handling
⚙️ Easy scaling via EC2 launch templates or Terraform
What the Script Does
The bootstrap script does everything you’d normally do by hand — but smarter.
Key Features:
Installs MongoDB, Pritunl, AWS CLI, and required packages
sudo tee /etc/apt/sources.list.d/pritunl.list << EOF
deb http://repo.pritunl.com/stable/apt jammy main
EOF
curl https://raw.githubusercontent.com/pritunl/pgp/master/pritunl_repo_pub.asc | sudo apt-key add -
sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list << EOF
deb https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse
EOF
wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -
sudo apt update
sudo apt --assume-yes upgrade
sudo ufw disable
sudo apt -y install pritunl mongodb-org unzip jq
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
sudo systemctl enable mongod
sudo systemctl start mongod
sleep 10Waits for MongoDB to be fully operational
until nc -z localhost 27017; do
echo "Waiting for MongoDB to be ready on port 27017..."
sleep 2
done
echo "MongoDB is ready!"Checks if MongoDB is already configured
if [[ "$CONFIGURED_URI" == "mongodb://localhost:27017/pritunl" ]]; then
echo "MongoDB already configured correctly."
elseConfigures Pritunl to use MongoDB (if not already)
sudo pritunl set-mongodb mongodb://localhost:27017/pritunlSaves credentials to AWS Secrets Manager
# Generate default admin password
SETUP_KEY=$(sudo pritunl setup-key)
DEFAULT_OUTPUT=$(sudo pritunl default-password)
# Parse username and password
USERNAME=$(echo "$DEFAULT_OUTPUT" | grep 'username:' | awk '{print $2}')
PASSWORD=$(echo "$DEFAULT_OUTPUT" | grep 'password:' | awk '{print $2}')
echo "Pritunl Admin Username: $USERNAME"
echo "Pritunl Admin Password: $PASSWORD"
echo "Pritunl Admin Password: $SETUP_KEY"
SECRET_NAME="pritunl-credentials"
# Create secret JSON
SECRET_STRING=$(jq -n --arg user "$USERNAME" --arg pass "$PASSWORD" --arg setup_key $SETUP_KEY '{"Username":$user,"Password":$pass,"Setup_Key":$setup_key}')
# Check if secret exists
aws secretsmanager describe-secret --secret-id "$SECRET_NAME" > /dev/null 2>&1
# Save and update secret manager
if [ $? -eq 0 ]; then
echo "Secret exists, updating..."
aws secretsmanager put-secret-value --secret-id "$SECRET_NAME" --secret-string "$SECRET_STRING"
else
echo "Creating new secret..."
aws secretsmanager create-secret --name "$SECRET_NAME" --secret-string "$SECRET_STRING"
fiIdempotent — Safe to re-run on reboot or instance replacement
MongoDB Readiness: The Sneaky Pitfall
One issue we ran into early on: Just because mongod is running doesn’t mean it’s ready.
we solved this by using a small loop:
until nc -z localhost 27017; do
echo "Waiting for MongoDB..."
sleep 2
doneThis ensures pritunl set-mongodb doesn’t silently fail.
Idempotent Configuration (a.k.a. “Don’t Break on Reboot”)
if [ -f /etc/pritunl.conf ]; then
CONFIGURED_URI=$(jq -r '.mongodb_uri' /etc/pritunl.conf)
fi
if [[ "$CONFIGURED_URI" != "mongodb://localhost:27017/pritunl" ]]; then
sudo pritunl set-mongodb mongodb://localhost:27017/pritunl
sudo systemctl restart pritunl
fiThis means you can:
Update the script
Add new features
Re-run it safely It will only reconfigure what’s necessary.
AWS Secrets Manager Integration
You don’t want to leave admin passwords floating in logs or bash history. So the script uses jq + AWS CLI to push them to Secrets Manager:
SECRET_STRING=$(jq -n \
--arg user "$USERNAME" \
--arg pass "$PASSWORD" \
--arg setup_key $SETUP_KEY \
'{"Username":$user,"Password":$pass,"Setup_Key":$setup_key}')If the secret already exists, it’s updated. If not, it’s created.
You now have centralized, secure access to your VPN admin credentials.
Real-World Use Cases
This setup is now part of our:
EC2 Launch Templates
DR/Failover scripts
Ephemeral VPN instances for contractor access
Whether you’re spinning up a dev VPN or securing a remote team, this works beautifully.
Final Thoughts
This started with a frustrating midnight incident and turned into one of the most satisfying automations we’ve written.
🔒 Secure, 🧠 Smart, ⚡ Fast.
No more manual SSH, no more forgotten credentials, and no more late-night logins to fix something that should’ve “just worked.”
Want the script, Obviously yes?
Let us know in the comments or message us — happy to share the full version or help tailor it to your stack!
Reply