Deploying reliable L2TP/IPsec VPN servers consistently across multiple hosts can be time-consuming if done manually. Automation with Ansible not only speeds up provisioning but also enforces repeatability, security best practices, and easy scaling. This article walks through a production-ready approach to deploy L2TP VPNs using Ansible, with concrete examples including playbooks, roles, templates, and operational guidance.
Why Ansible for L2TP/IPsec?
Ansible is agentless, idempotent, and well-suited for procedural configuration tasks required by L2TP/IPsec stacks (xl2tpd + strongSwan or Libreswan). Using Ansible you gain:
- Reproducibility: identical servers from a single source of truth.
- Security: secrets managed via ansible-vault and consistent hardening.
- Scalability: roll out changes to tens or thousands of instances with a single command.
- Observability: clear tasks, idempotent outcomes, and easy integration into CI/CD pipelines.
Architecture overview
The most common Linux L2TP/IPsec architecture uses:
- IPsec: strongSwan (or Libreswan) for IKEv1/IKEv2 and ESP tunneling.
- L2TP: xl2tpd for Layer 2 tunnel management.
- PPP: pppd for client authentication and IP address assignment.
- Firewall & NAT: iptables or nftables to allow IPsec ports and NAT client traffic to the internet.
This guide assumes Debian/Ubuntu or RHEL/CentOS-based servers; tasks differ slightly per distribution.
Directory and role layout
Use a role-based structure to keep tasks modular. Example layout:
- roles/l2tp/tasks/main.yml
- roles/l2tp/templates/ipsec.conf.j2
- roles/l2tp/templates/ipsec.secrets.j2
- roles/l2tp/templates/xl2tpd.conf.j2
- roles/l2tp/templates/options.xl2tpd.j2
- roles/l2tp/vars/main.yml
- playbooks/deploy-l2tp.yml
Key variables to manage
Define variables in roles/l2tp/vars/main.yml or group_vars. Important ones:
- vpn_public_ip — server public IP
- vpn_ip_range — pool for VPN clients (e.g., 192.168.100.0/24)
- vpn_users — list of username/password entries or callouts to RADIUS/LDAP
- ipsec_psk — pre-shared key (store in ansible-vault)
- nat_outbound_interface — interface used for SNAT (e.g., eth0)
- enable_ipv6 — boolean if IPv6 support is required
Security: secrets and hardening
Never commit PSKs or user passwords in plain text. Use ansible-vault to encrypt sensitive variables and files (ipsec.secrets, ppp secrets). Example encryption command:
ansible-vault encrypt roles/l2tp/vars/secret_vars.yml
Additionally, enforce firewall rules to only open the necessary ports:
- UDP/500 (IKE)
- UDP/4500 (NAT-T)
- IP protocol 50 (ESP) — handled by kernel but ensure firewall allows
Example playbook (deploy-l2tp.yml)
The top-level playbook targets your VPN hosts and calls the l2tp role:
<pre>
– hosts: vpn_servers
become: true
vars_files:
– roles/l2tp/vars/secret_vars.yml # encrypted with ansible-vault
roles:
– l2tp
</pre>
Tasks overview (roles/l2tp/tasks/main.yml)
Key tasks the role must perform:
- Install required packages (strongswan, xl2tpd, ppp, iptables/nftables).
- Deploy templated configuration files via j2 templates (ipsec.conf, ipsec.secrets, xl2tpd.conf, options.xl2tpd).
- Configure sysctl for IP forwarding and disable redirects.
- Set up firewall/NAT rules and persistent saving.
- Enable and start services, ensure idempotent restarts on config change using handlers.
- Create local ppp secrets or integrate with a backend (RADIUS/LDAP) when required.
Package installation
Install platform-specific packages. Example task (Debian/Ubuntu):
<pre>
– name: Install L2TP/IPsec packages (Debian)
apt:
name:
– strongswan
– xl2tpd
– ppp
– iptables
state: present
update_cache: yes
when: ansible_os_family == ‘Debian’
</pre>
System settings
Enable IPv4 forwarding and disable ICMP redirects. Use sysctl module to be idempotent:
<pre>
– name: Ensure sysctl values for VPN
sysctl:
name: “{{ item.key }}”
value: “{{ item.value }}”
state: present
reload: yes
with_items:
– { key: ‘net.ipv4.ip_forward’, value: 1 }
– { key: ‘net.ipv4.conf.all.accept_redirects’, value: 0 }
– { key: ‘net.ipv4.conf.all.send_redirects’, value: 0 }
</pre>
Templates
Use Jinja2 templates for configuration files. Example ipsec.conf template skeleton:
<pre>
config setup
charondebug=”cfg 2, knl 2, ike 2″
conn L2TP-PSK
authby=psk
pfs=no
auto=add
keyingtries=3
rekey=no
type=transport
left=%any
leftid={{ vpn_public_ip }}
leftsubnet=0.0.0.0/0
right=%any
rightprotoport=17/%any
ike=aes256-sha1-modp1024!
esp=aes256-sha1!
dpdaction=clear
forceencaps=yes
</pre>
And ipsec.secrets (stored with ansible-vault):
<pre>
{{ vpn_public_ip }} : PSK “{{ ipsec_psk }}”
</pre>
XL2TPD and PPP options require precise syntax to ensure compatibility with Windows, macOS, iOS, and Android clients. Example options.xl2tpd.j2:
<pre>
require-mschap-v2
ms-dns 8.8.8.8
mtu 1400
mru 1400
noccp
auth
lock
proxyarp
name l2tpd
ipcp-accept-local
ipcp-accept-remote
</pre>
Adjust DNS (ms-dns) and MTU for environments with double-encapsulation (IPsec + L2TP).
Firewall and NAT
A robust rule set is critical. The minimal iptables rules:
- Allow UDP/500 and UDP/4500 to the host.
- Allow established and related traffic.
- Set up MASQUERADE/SNAT for the VPN client subnet out via the public interface.
Example Ansible task to configure iptables (simplified):
<pre>
– name: Allow IKE and NAT-T
iptables:
chain: INPUT
protocol: udp
destination_port: “{{ item }}”
jump: ACCEPT
with_items:
– 500
– 4500
– name: Allow ESP protocol
iptables:
chain: INPUT
protocol: 50
jump: ACCEPT
– name: Allow related established
iptables:
chain: INPUT
ctstate: ESTABLISHED,RELATED
jump: ACCEPT
– name: NAT masquerade for vpn clients
iptables:
table: nat
chain: POSTROUTING
out_interface: “{{ nat_outbound_interface }}”
source: “{{ vpn_ip_range }}”
jump: MASQUERADE
</pre>
For production use, incorporate iptables-save/restore or use nftables backend and persist rules across reboots.
User authentication: local vs backend
Two common options:
- Local PPP Secrets: store username/password in /etc/ppp/chap-secrets. Manage via Ansible templating or templates rendered from encrypted vars.
- RADIUS/LDAP: delegate authentication to a centralized backend for multi-server deployments. StrongSwan supports EAP and RADIUS; xl2tpd PPP can proxy to a radius server via pppd plugins.
Example chap-secrets entry format to be generated by Ansible:
<pre>
{{ item.username }} l2tpd {{ item.password }} *
</pre>
Handlers and service management
Use handlers to restart services only when templates change. Example handlers:
<pre>
– name: restart strongswan
service:
name: strongswan
state: restarted
– name: restart xl2tpd
service:
name: xl2tpd
state: restarted
</pre>
Notify these handlers from tasks that deploy ipsec.conf, ipsec.secrets, xl2tpd.conf, or PPP options.
Idempotency and testing
Ansible tasks should be idempotent — running the playbook multiple times must result in the same state with no unexpected restarts. Validate with:
- ansible-playbook –check to run in dry-run mode (where supported).
- Use molecules or CI jobs to provision ephemeral VMs and run acceptance tests.
- Manual connection tests: configure a client (Windows/macOS/Linux) and validate IKE negotiation, L2TP session establishment, and DNS/traffic forwarding.
Useful commands during troubleshooting on the server:
- strongswan statusall
- journalctl -u strongswan -f
- journalctl -u xl2tpd -f
- tcpdump -i port 500 or port 4500 or proto 50
Scaling and multi-region considerations
For multi-host deployments you might want to:
- Use a template-driven inventory (dynamic inventory) that references cloud metadata.
- Centralize user management with RADIUS/LDAP so adding/removing users is immediate across all endpoints.
- Automate certificate management with ACME/PKI if moving to certificate-based authentication (recommended over PSK for larger environments).
- Automate monitoring (Prometheus exporters for system metrics, logs forwarded to ELK/CloudWatch) and alerting for connection failures or high CPU/RAM usage.
Operational tips
Some practical recommendations from production experiences:
- Prefer strongSwan with IKEv2 and certificates for long-term scalability; IKEv1 PSK + L2TP is simple but less flexible.
- Keep MTU/MRU conservative (around 1400) to avoid fragmentation with double encapsulation.
- Automate backups of /etc/strongswan and /etc/ppp and tie them into config management for quick restore.
- Rotate PSKs and credentials periodically and use ansible-vault with key rotation procedures.
- Log verbosely in staging but disable excessive debug in production to avoid disk fill-ups.
Troubleshooting checklist
If clients cannot connect, check in order:
- Networking: ensure UDP/500 and UDP/4500 reach the server from the client (use tcpdump on host).
- IP forwarding: sysctl net.ipv4.ip_forward == 1.
- Firewall: iptables/nftables rules allow IKE/ESP and NAT is applied.
- Secrets: ipsec.secrets and chap-secrets match client configuration; verify ansible-vault decrypted values on host.
- Service status: strongswan and xl2tpd are running and not reporting errors in systemd logs.
Example quick-start checklist for a new host
- Provision VM with public IP and minimal OS image.
- Ensure outbound package mirrors accessible.
- Run ansible-playbook playbooks/deploy-l2tp.yml –limit new-host
- Confirm services running and open ports.
- Test client connection and validate traffic flows as expected.
Automating L2TP/IPsec VPNs with Ansible reduces manual steps, improves security posture, and simplifies scaling. By separating configuration templates, encrypted secrets, and operational handlers, you achieve a maintainable and auditable deployment process suitable for site operators, enterprises, and developers managing remote access or client VPN services.
For more detailed guides, templates, and a sample Ansible role repository tailored for different Linux distributions, visit Dedicated-IP-VPN at https://dedicated-ip-vpn.com/. The site provides articles and resources for secure, high-performance VPN deployments.