🔐 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 10
  • Waits 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."
else
  • Configures Pritunl to use MongoDB (if not already)

sudo pritunl set-mongodb mongodb://localhost:27017/pritunl
  • Saves 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"
fi
  • Idempotent — 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
done

This 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
fi

This 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

or to participate.