Why This Baseline Helps π‘
If you're managing remote access for a distributed team and need site-to-site connectivity with partners running legacy IPsec, you've probably felt the pain of maintaining two separate VPN stacks. One modern (WireGuard via Firezone), one classic (strongSwan/Libreswan), both fighting for the same public IP and firewall rules.
This guide walks you through a single Terraform deployment that gives you:
- Two access patterns, one deployment: WireGuard for your remote users, Classic IPsec for partner networks.
- Zero secrets in git: All credentials live in GCP Secret Manager, referenced via data sources.
- Production-ready from day one: Load balancer health checks, automated backups, OpenSSF Scorecard hardening, and cost-saving VM schedulers.
By the end, you'll have a working VPN gateway on GCP that your team can connect to via the Firezone mobile/desktop clients, while your partners connect via standard IPsec tunnelsβall managed as code.
What You're Building ποΈ
Here's the architecture at a glance:
| Component | Purpose | Key Features |
|---|---|---|
| Firezone | Modern WireGuard VPN with web UI | User management, device enrollment, audit logs |
| strongSwan (optional) | Classic IPsec for site-to-site tunnels | Partner compatibility, IKEv2/ESP support |
| GCP Load Balancer | Health-checked traffic routing | Automatic failover, regional redundancy |
| Cloud Scheduler | Cost optimization | Auto-start 6am, auto-stop 11pm (configurable) |
| Secret Manager | Credential storage | Zero plaintext secrets in code |
Prerequisites Checklist β
Before you start, make sure you have:
- GCP Project with billing enabled
- Terraform v1.9+ installed locally
-
gcloud CLI authenticated (
gcloud auth application-default login) -
Service Account with roles:
roles/compute.instanceAdmin.v1roles/cloudscheduler.admin
Note: If you plan to enable site-to-site VPN tunnels, you'll also need to create secrets in GCP Secret Manager for the IPsec pre-shared keys (format:
vpn-psk-{tunnel_name}). See the VPN Gateway module documentation for details.
Step 1: Clone and Configure π¦
git clone https://github.com/dieguezz/terraform-gcp-vpn.git
cd terraform-gcp-vpn
cp terraform.tfvars.example terraform.tfvars
Edit terraform.tfvars with your values:
project_id = "your-gcp-project-id"
region = "us-central1"
zone = "us-central1-a"
# Network
network_name = "vpn-network"
subnet_name = "vpn-subnet"
subnet_cidr = "10.128.0.0/20"
# Firezone
firezone_hostname = "vpn.yourdomain.com"
# Scheduling (optional, remove to run 24/7)
enable_scheduling = true
start_schedule = "0 6 * * 1-5" # 6am Mon-Fri
stop_schedule = "0 23 * * 1-5" # 11pm Mon-Fri
timezone = "America/New_York"
Step 2: Initialize Terraform π§
terraform init
You'll see modules downloading:
Initializing modules...
- compute in modules/compute
- load_balancer in modules/load-balancer
- network in modules/network
- scheduler in modules/scheduler
- security in modules/security
- vpn_gateway in modules/vpn-gateway
Step 3: Review the Plan π
terraform plan
Terraform will show you what's about to be created. Pay attention to:
- Compute Engine instance (e2-medium with 50GB boot disk)
- VPC network and subnet with firewall rules for UDP 51820, 500, 4500
- Load balancer with health check on Firezone port 13000
-
Cloud Functions for start/stop scheduling (if
enable_scheduling = true) - IAM bindings for Secret Manager access
Step 4: Deploy π’
terraform apply
Type yes when prompted. The deployment takes ~5 minutes. Coffee break time β
Step 5: Access Firezone π
Once complete, Terraform outputs the load balancer IP:
Outputs:
firezone_url = "https://35.xxx.xxx.xxx"
load_balancer_ip = "35.xxx.xxx.xxx"
vpn_instance_name = "firezone-vpn-instance"
-
Point your DNS: Create an A record for
vpn.yourdomain.comβ35.xxx.xxx.xxx -
Wait 2-3 minutes for Firezone to initialize (check startup script logs with
gcloud compute instances get-serial-port-output) -
Open browser: Navigate to
https://vpn.yourdomain.com -
Login: Use your Google Workspace credentials (configured via
firezone_admin_emailandgoogle_workspace_domainvariables) - Enroll devices: Add users, generate WireGuard configs via the Firezone UI
Step 6: Site-to-Site VPN (Optional) π
This deployment also supports Classic IPsec tunnels for connecting with partner networks or legacy infrastructure. This is completely optional and disabled by default.
If you need this capability, the infrastructure includes a pre-configured strongSwan setup. To enable it:
- Set
enable_site_to_site_vpn = truein yourterraform.tfvars - Configure your tunnel parameters in the
vpn_tunnelsvariable - Create the required pre-shared keys in Secret Manager
For detailed configuration steps, see the VPN Gateway Module Documentation which includes complete examples and troubleshooting guides.
Security Hardening Applied π
This deployment follows OpenSSF Scorecard recommendations:
- β No secrets in code β all credentials in Secret Manager
- β Pinned dependencies β GitHub Actions use SHA hashes
- β SAST enabled β CodeQL scans on every PR
- β Least-privilege IAM β service account has minimal roles
- β Encrypted boot disk β GCP default encryption at rest
- β Regular backups β VM snapshots via Cloud Scheduler (roadmap item)
Cost Breakdown (Monthly Estimate) π°
| Resource | Type | Cost (USD/month) |
|---|---|---|
| Compute Engine | e2-medium (50% usage with scheduler) | ~$12 |
| Load Balancer | Network LB with health checks | ~$18 |
| VPC | Egress traffic (10GB assumed) | ~$1 |
| Secret Manager | 4 secrets, 10 accesses/day | <$1 |
| Cloud Scheduler | 2 jobs (start/stop) | Free tier |
| Total | ~$32/month |
Savings: Without the scheduler (24/7 runtime), the e2-medium costs ~$24/month. The scheduler saves you ~$12/month (50% reduction).
Troubleshooting π
| Symptom | Likely Cause | Fix |
|---|---|---|
terraform apply fails with "secret not found" |
VPN tunnel enabled but PSK secret missing | Create secret: gcloud secrets create vpn-psk-{tunnel_name} or disable site-to-site VPN |
| Can't access Firezone UI after 5 min | Startup script still running | Check gcloud compute instances get-serial-port-output for errors |
| WireGuard clients can't connect | Firewall rules not applied | Verify gcloud compute firewall-rules list includes allow-wireguard
|
| Google Workspace SSO not working | Domain mismatch or OAuth misconfigured | Verify google_workspace_domain matches your workspace and check Firezone OAuth settings |
| Instance stops unexpectedly | Scheduler enabled but wrong timezone | Verify timezone variable in terraform.tfvars
|
What's Next? π―
Now that you have a working baseline:
- Add users: Use the Firezone web UI to invite team members and generate device configs
-
Monitor traffic: Enable VPC Flow Logs (
gcloud compute networks subnets update --enable-flow-logs) -
Automate backups: Extend the
schedulermodule to snapshot the boot disk weekly - Multi-region failover: Deploy a second instance in another region, use Cloud DNS routing
- Audit logs: Forward Firezone logs to Cloud Logging for compliance
Key Takeaways π
- Hybrid VPN stacks don't have to be messy. One Terraform deployment can handle both modern WireGuard and legacy IPsec.
- Zero secrets in git is achievable. GCP Secret Manager + Terraform data sources = clean commit history.
- Cost control matters. A simple scheduler can cut your VM bill in half for dev/staging environments.
- Security is a journey. OpenSSF Scorecard gives you a roadmapβautomated fixes get you 80% of the way.
Repository & Resources π
- GitHub: dieguezz/terraform-gcp-vpn
- Firezone Docs: firezone.dev/docs
- strongSwan Wiki: strongswan.org
- OpenSSF Scorecard: securityscorecards.dev
Got questions or want to share your VPN war stories? Drop a comment below or open an issue on GitHub. If this helped you, a β on the repo keeps me caffeinated for the next guide!
Next in series: Architecture Deep Dive β How the Modules Talk to Each Other (and Why It Matters) ποΈ
Top comments (0)