IPsec Tunnels

Draft

Table of Contents

Overview

WorksSourceTypeTargetRouterTechnology
YesAzure Cloud Linux VMS2S LANOn-PremiseMikrotikStrongswan
YesAzure Cloud Linux VMS2S SNAT IP -> LANOn-PremiseMikrotikStrongswan and IPTables
YesAzure Cloud Linux VMS2S SNAT IP -> DNAT IPOn-PremiseMikrotikStrongswan, IPTables and Mikrotik Firewall
YesAzure Cloud VNetS2S LANOn-PremiseMikrotikAzure VPN Gateway
❌ NoAzure Cloud VNetS2S SNATOn-PremiseMikrotikAzure VPN Gateway, SNAT Egress Rules

IPsec

IPsec (Internet Protocol Security) is a suite of protocols used to secure Internet Protocol (IP) communications by authenticating and encrypting each IP packet in a communication session. It operates at the network layer (Layer 3) of the OSI model, which means it can protect all traffic crossing an IP network, regardless of the application.

Core Functions of IPsec

  1. Confidentiality – Encrypts data to prevent eavesdropping.
  2. Integrity – Ensures data is not altered during transmission.
  3. Authentication – Verifies the identities of the communicating parties.
  4. Anti-replay – Prevents attackers from re-sending captured packets.

IPsec protocols

AH (Authentication Header) Provides integrity and authentication. Does not provide encryption. Protects against spoofing and tampering. Protocol number: 51

ESP (Encapsulating Security Payload) Provides encryption, integrity, and authentication. Protocol number: 50

IPsec ports

UDP 500 and UDP 4500 are mandatory. Otherwise, tunnel will never establish.

ESP is optional if you use NAT-T (which StrongSwan normally negotiates automatically).

In most cloud setups (like Azure), ESP (IP protocol 50) often does not pass reliably — that's why NAT-T (UDP 4500 encapsulation) is the standard way.

You don't need to open any inbound ports unless partner wants to initiate tunnel.

Modes of Operation

Transport Mode Encrypts only the payload of the IP packet. Used for end-to-end communication (e.g., host to host). IP header remains visible.

Tunnel Mode Encrypts the entire IP packet. A new IP header is added. Commonly used in VPNs, especially between gateways (e.g., site-to-site).

Security Associations (SA)

An SA is a one-way logical connection that defines the parameters (keys, algorithms, etc.) for IPsec communication. Managed via IKE (Internet Key Exchange) – Negotiates SAs and key material. Two versions: IKEv1 and IKEv2.

Typical Use Cases

VPNs (Virtual Private Networks) for secure site-to-site or remote access. Secure communication over untrusted networks (e.g., Internet). Protecting data in government/military communications.

Azure Cloud & On-Premise Datacenter

Important: Azure Virtual Network Gateway connections does not support Traffic Selectors for IPSec Policy based tunnels when at least one SNAT Egress/Ingress rule attached. That makes Azure Virtual Network Gateway useless when On-Premise networks has the same subnet address range as your Azure VNet has. For example 10.0.0.0/16 for both sides!

To achieve one-way access — where my Azure Kubernetes pods can connect to the partner's on-prem RabbitMQ, but they cannot initiate connections back to my hosts or pods — I should use a site-to-site IPsec VPN with asymmetric routing and strict firewall controls on my side.

My Side (Azure)                      Their Side (On-Premise)
------------------                   ----------------------
AKS Pods (RabbitMQ client)           RabbitMQ Server (on-prem)
    ↓                                      ↑
Azure VNet                           On-Prem Network (e.g., 192.168.100.0/24)
    ↓                                      ↑
Azure VPN Gateway  --------------->  On-Prem VPN device (e.g., pfSense, Cisco)
    ↕
Optional: Azure Firewall (SNAT or filtering)

Create Public IP And VPN Gateway subnet

resource "azurerm_public_ip" "partners_vpn_gateway_dev_public_ip" {
  name                = "partners_vpn_gateway_dev_public_ip"
  location            = data.azurerm_resource_group.main_rg.location
  resource_group_name = data.azurerm_resource_group.main_rg.name
  allocation_method   = "Static"
  sku                 = "Standard"
}
resource "azurerm_subnet" "vpn_gateway_dev" {
  name                 = "GatewaySubnet"
  resource_group_name  = data.azurerm_resource_group.main_rg.name
  virtual_network_name = azurerm_virtual_network.vnet_dev.name
  address_prefixes     = ["10.1.0.0/24"]
}

Create Azure Virtual Network Gateway

To create a Virtual Network Gateway you should have at least Network Contributor role assigned in Azure subscriptions.

Virtual network gateway can not be created if the virtual network already uses remote gateways over peering. You cannot have both a Virtual Network Gateway and a Virtual Hub (from Azure Virtual WAN) on the same virtual network (VNet).

Azure Virtual WAN abstracts the underlying VNets and uses its own Virtual Hub architecture, which is different from the classic Virtual Network Gateway setup. Once a VNet is connected to a Virtual WAN hub, you no longer need (or can have) a VPN Gateway or ExpressRoute Gateway in that VNet.

You must migrate your Azure Point-to-Site VPN to Virtual Network Gateway before building Site-to-Site IPSec VPN connection.

ScenarioVirtual WAN (with Virtual Hub)Virtual Network Gateway (Traditional)
VNet directly contains VPN GatewayNot allowedAllowed
VNet connected to Virtual HubAllowedYou cannot add a VPN Gateway to this VNet
Point-to-Site VPN supportYes, via Virtual HubYes, via Gateway
Can coexist in same VNetNoOnly one VPN Gateway per VNet
resource "azurerm_virtual_network_gateway" "partners_vpn_gateway_dev" {
  name                = "partners_vpn_gateway_dev"
  location            = data.azurerm_resource_group.main_rg.location
  resource_group_name = data.azurerm_resource_group.main_rg.name
  type                = "Vpn"
  vpn_type            = "RouteBased"
  active_active       = false
  enable_bgp          = false
  sku                 = "VpnGw2"

  ip_configuration {
    name                          = "vnetGatewayConfig"
    public_ip_address_id          = azurerm_public_ip.partners_vpn_gateway_dev_public_ip.id
    private_ip_address_allocation = "Dynamic"
    subnet_id                     = azurerm_subnet.vpn_gateway_dev.id
  }
}

Basic SKU ~8 minutes. VpnGw1AZ ~28 minutes.

A virtual network gateway SKU of Standard or higher is required for Ipsec Policies support on virtual network gateway. Upgrade from Basic SKU not allowed. Actually you can't use Standard SKU too. The allowed SKUs are VpnGw1AZ, VpnGw2AZ, VpnGw3AZ, VpnGw4AZ, VpnGw5AZ, VpnGw1, VpnGw2, VpnGw3, VpnGw4, VpnGw5. Standard Public IPs associated with VPN gateways with non-AZ VPN SKU cannot have zones configured, forcing to use VpnGw?AZ SKU or disable zones in IP configuration.

Public IP Standard SKU + Zones and VpnGw?AZ or Public IP Standard SKU and VpnGw1.

VpnGw1 is not supporting SNAT!

Create Local Network Gateway in Azure

Create a local network gateway to represent the on-premises site that you want to connect to a virtual network. The local network gateway specifies the public IP address of the VPN device and IP address ranges located on the on-premises site.

resource "azurerm_local_network_gateway" "partners_onprem_local_network_gateway_dev" {
  name                = "partners_onprem_local_network_gateway_dev"
  location            = data.azurerm_resource_group.main_rg.location
  resource_group_name = data.azurerm_resource_group.main_rg.name
  gateway_address     = "???.???.???.???" # The remote gateway IP address to connect with.

  address_space = ["198.???.???.???/32"] # The list of string CIDRs representing the address spaces the gateway exposes.
}

Azure VM with IP 10.1.2.66 will have its source IP translated to an address in 100.???.???.???/29 when traffic goes out through the IPsec tunnel. The on-premises side will see traffic coming from 100.???.???.???, not 10.1.2.66. On the on-prem side, they must accept and route return traffic to 100.???.???.???/29.

Create Azure Virtual Network Gateway Connection

Obtain Details from Partner Company.

  • Public IP of their VPN device
  • On-prem CIDRs (e.g., 192.168.100.0/24)
  • IPsec/IKE parameters:
    • IKE version (IKEv2 recommended)
    • Pre-shared key (PSK)
    • Encryption/integrity algorithms
    • DH Group (e.g., Group 14, 20)
    • Lifetime

Ask if they can whitelist your IPs (e.g., NATed IP if needed).

Usually you will receive a document with IPSec policy connection parameters. In those documents ipsec_integrity often called Hash algorithm, and pfs_group will be a Diffie-Hellman group for PFS or Perfect Forward Secrecy.

Create a VPN gateway connection between the virtual network gateway for the virtual network, and the local network gateway for the on-premises site.

resource "azurerm_virtual_network_gateway_connection" "partners_onprem_vpn_connection" {
  name                            = "partner_vpn_connection"
  location                        = data.azurerm_resource_group.main_rg.location
  resource_group_name             = data.azurerm_resource_group.main_rg.name
  type                            = "IPsec"
  connection_protocol             = "IKEv2"
  virtual_network_gateway_id      = azurerm_virtual_network_gateway.partners_vpn_gateway_dev.id
  local_network_gateway_id        = azurerm_local_network_gateway.pertners_onprem_local_network_gateway_dev.id
  shared_key                      = var.shared_key
  enable_bgp                      = false
  use_policy_based_traffic_selectors = false

  ipsec_policy {
    sa_lifetime            = 86400
    sa_datasize            = 102400000
    ipsec_encryption       = "AES256"
    ipsec_integrity        = "SHA256"
    ike_encryption         = "AES256"
    ike_integrity          = "SHA256"
    dh_group               = "ECP384"
    pfs_group              = "ECP384"
  }
}

In typical IPsec VPN setups between your infrastructure and a partner/provider, the pre-shared key (PSK) is mutually agreed upon — it’s not autogenerated unless you both support some automation tooling or use certificates. You need to coordinate with partner to either: receive a PSK from them, or propose one yourself and send it to them securely (e.g., via encrypted email or secure portal).

This is how you can generate shared key for now:

$ openssl rand -base64 32

In some documents you will see Authenticated ESP Tunnel requirement. It means that ipsec_policy with encryption & integrity must be set in virtual network connection.

Parameter breakdown by Phase

ParameterValuePhaseDescription
sa_lifetime_sec36002IPsec SA lifetime in seconds.
sa_data_size_kb1024000002Maximum volume of data before rekeying (IPsec).
ipsec_encryptionAES2562Encryption algorithm for IPsec tunnel.
ipsec_integritySHA2562Integrity algorithm for IPsec.
ike_encryptionAES2561Encryption for IKE (control plane tunnel).
ike_integritySHA2561Integrity check for IKE messages.
dh_groupECP3841Diffie-Hellman group for IKE key exchange.
pfs_groupECP3842DH group used for Perfect Forward Secrecy in IPsec tunnel (child SA).

SKU Limits

SKUMax Site-to-Site Tunnels
VpnGw110
VpnGw230
VpnGw330
VpnGw5100

Traffic Routing

When you create Local Network Gateway the Azure automatically add effective routes to the network interfaces. In most cases you do not need to add user defined routes (UDR).

$ az network nic show-effective-route-table --name <NIC NAME> --resource-group <RG NAME>

Routing in Azure

Gateway SNAT for VM

If you have network overlaping when your Azure cloud VNet and partner's On-Premise DC both use 10.0.0.0/24 - You should make all traffic from your Azure side (e.g., VMs or AKS pods) appear to come from the IP range for example 100.64.0.16/29 when it goes through the IPsec tunnel. This avoids IP overlap and simplifies their side.

Topologies

Important: Azure Virtual Network Gateway does not support SNAT for Policy-based IPSec Tunnels! Use Azure Linux VM with Strongswan and Iptables in that case.

NAT rules only work in route-based VPN gateways (VpnGw1+).

NAT rules do not support port-level translation — they only apply to IP-level NAT.

Create Azure Network Gateway SNAT Egress rule.

data "azurerm_virtual_network_gateway" "vpn_gateway_dev" {
  name                = azurerm_virtual_network_gateway.vpn_gateway_dev.name
  resource_group_name = data.azurerm_resource_group.main_rg.name
}

resource "azurerm_virtual_network_gateway_nat_rule" "snat_example" {
  name                       = "snat-rule"
  resource_group_name        = data.azurerm_resource_group.main_rg.name

  virtual_network_gateway_id = azurerm_virtual_network_gateway.vpn_gateway_dev.id
  ip_configuration_id        = data.azurerm_virtual_network_gateway.vpn_gateway_dev.ip_configuration[0].id
  mode                       = "EgressSnat"
  type                       = "Dynamic"

  external_mapping {
    address_space = "100.64.0.18/32"    # External SNAT IP your packets must have while outgoing.
  }

  internal_mapping {
    address_space = "10.1.2.0/24"       # Internal VM Private IP
  }
}

NAT works per connection, so you must associate NAT rules with VPN Connections, not the gateway alone.

Add your SNAT rule to the azurerm_virtual_network_gateway_connection.partners_onprem_vpn_connection resource.

...
  egress_nat_rule_ids = [
    azurerm_virtual_network_gateway_nat_rule.snat_example.id,
  ]

Linux VM With IPtables As SNAT Gateway

Install Strongswan and IPSec.

IPSec config setup /etc/ipsec.conf:

config setup
  charondebug="ike 2, knl 2, cfg 2, net 2, esp 2, dmn 2,  mgr 2"

conn azure-to-onprem
  keyexchange=ikev2
  auto=start
  type=tunnel
  authby=psk

  left=%defaultroute                    # This VM
  leftid=<AZURE FIREWALL PUBLIC IP>
  #leftsubnet=10.1.0.0/16               # Private subnet without SNAT
  leftsubnet=100.64.0.18/32             # SNAT

  right=<ON-PREMISE FIREWALL/ROUTER PUBLIC IP>
  rightsubnet=192.168.88.0/24           # On-premise LAN

  ike=aes256-sha256-ecp384
  esp=aes256-sha256

  ikelifetime=30m                 # 1800 seconds
  lifetime=30m                    # 1800 seconds
  rekeymargin=3m                  # Start rekey 3 minutes before expiry (safe default)
  rekeyfuzz=10%

  dpdaction=restart
  dpddelay=30s
  dpdtimeout=120s

  keylife=3600s

IPSec secrets /etc/ipsec.secrets:

<AZURE FIREWALL PUBLIC IP> <ON_PREMISE FIREWALL/ROUTER PUBLIC IP> : PSK "PASSWORD"

Enable IP forwarding on the VM NIC and Linux OS. Add IPtables SNAT rules:

# Enable IP forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward

# SNAT all traffic going to RabbitMQ
iptables -t nat -A POSTROUTING -s 10.1.1.66/32 -d 192.168.88.1/32 -j SNAT --to-source 100.64.0.18
iptables -t nat -S POSTROUTING

You can have several records for different target IPs:

iptables -t nat -L POSTROUTING -n --line-numbers
Chain POSTROUTING (policy ACCEPT)
num  target     prot opt source               destination
1    SNAT       all  --  10.1.1.66            198.18.170.151       to:100.64.0.18
2    SNAT       all  --  10.1.1.66            192.168.88.1         to:100.64.0.18

To remove your IPTABLES rule:

iptables -t nat -L POSTROUTING --line-numbers -n -v
iptables -t nat -D POSTROUTING <RULE_NUMBER>

Azure Firewall should be configured to allow outbound IPSec connection and outbound HTTP connection through the tunnel.

resource "azurerm_firewall_network_rule_collection" "dev-main-fw-net-rule-collection-allow" {
  name                = "dev-main-fw-rule-collection-allow"
  azure_firewall_name = azurerm_firewall.dev-main-fw.name
  resource_group_name = data.azurerm_resource_group.main_rg.name
  priority            = 100
  action              = "Allow"

  # To establish IPSec tunnels
  rule {
    name = "strongswan-onprem-ipsec-dev"

    source_addresses = [
      "10.1.1.66/32",
    ]

    destination_ports = [
      "500",
      "4500",
    ]

    destination_addresses = [
      "<ON-PREMISE FIREWALL/ROUTER PUBLIC IP>",
    ]

    protocols = [
      "UDP",
    ]
  }

  # To access on-premise resources through IPSec tunnels
  rule {
    name = "strongswan-onprem-ipsec-dev-http"

    source_addresses = [
      "10.1.1.66/32",
    ]

    destination_ports = [
      "80",
    ]

    destination_addresses = [
      "192.168.88.1",   # On-premise router Private IP
    ]

    protocols = [
      "TCP",
    ]
  }

Mikrotik Firewall should be configured to allow inbound IPSec connection and inbound HTTP connection to its Webfig UI.

[admin@MikroTik] > /ip firewall filter print
 1    ;;; IPSec IKE and NAT-T - From Azure Strongswan VM through the Azure Firewall to Mikrotik
      chain=input action=accept protocol=udp src-address=<AZURE FIREWALL PUBLIC IP> dst-port=500,4500 log=no log-prefix=""
 2    ;;; IPSec IKE and NAT-T - From Azure Strongswan VM SNAT IP Allow HTTP access to Mikrotik
      chain=input action=accept protocol=tcp src-address=100.64.0.18 dst-port=80 log=no log-prefix=""

Creating peer on Mikrotik actions: Profile -> Peer -> Identity -> Proposal -> Policy.

Mikrotik IPSec Profile:

[admin@MikroTik] > /ip ipsec profile print
Flags: * - default
 0 * name="default" hash-algorithm=sha1 enc-algorithm=aes-128,3des dh-group=modp2048,modp1024 lifetime=1d
     proposal-check=obey nat-traversal=yes dpd-interval=2m dpd-maximum-failures=5

 1   name="azure-dev" hash-algorithm=sha256 enc-algorithm=aes-256 dh-group=ecp384 lifetime=30m proposal-check=obey
     nat-traversal=yes dpd-interval=2m dpd-maximum-failures=4

I show you 2 peers to indicate that Azure VPN Gateway has another Public IP address of it's own. For this VM based configuration use only peer id=0.

Mikrotik IPSec Peers:

[admin@MikroTik] > /ip ipsec peer print
Flags: X - disabled, D - dynamic, R - responder
 0     ;;; Azure VM Strongswan IPSec Connections
       name="azure-dev-firewall" address=<AZURE FIREWALL> local-address=<MIKROTIK> profile=dev
       exchange-mode=ike2 send-initial-contact=yes

 1     ;;; Azure VPN Gateway Connections
       name="azure-dev-vpn-gateway" address=<AZURE VPN GATEWAY> local-address=<MIKROTIK> profile=dev
       exchange-mode=ike2 send-initial-contact=yes

Important: You should consider 2 facts about Azure Virtual Network Gateway:

  1. Azure VPN Gateway connection traffic is bypassing the Azure Firewall!
  2. Azure VPN Gateway will not send traffic selectors if you attach at least one SNAT Egress rule to Gateway Connection.

Mikrotik IPSec Indentity:

[admin@MikroTik] > /ip ipsec identity print
Flags: D - dynamic, X - disabled
 0    peer=azure-dev-firewall auth-method=pre-shared-key secret="<PASSWORD>"
      generate-policy=no

 1    peer=azure-dev-vpn-gateway auth-method=pre-shared-key secret="<PASSWORD>"
      generate-policy=no

Mikrotik IPSec Proposals:

[admin@MikroTik] > /ip ipsec proposal print
Flags: X - disabled, * - default
 0 X* name="default" auth-algorithms=sha1 enc-algorithms=aes-256-cbc,aes-192-cbc,aes-128-cbc lifetime=30m
      pfs-group=modp1024

 1    name="azure-dev-strongswan" auth-algorithms=sha256 enc-algorithms=aes-256-cbc lifetime=30m pfs-group=ecp384

Mikrotik IPSec Policy:

[admin@MikroTik] > /ip ipsec policy print
Flags: T - template, B - backup, X - disabled, D - dynamic, I - invalid, A - active, * - default
 #      PEE TUN SRC-ADDRESS                                   DST-ADDRESS                                   PROTOCOL
 0 T X*         ::/0                                          ::/0                                          all
 1   XI ;;; Azure Strongswan VM + Firewall
        far yes 192.168.88.0/24                               10.1.0.0/16                                   all
 2   A  ;;; Azure Strongswan VM + Firewall + SNAT
        far yes 192.168.88.0/24                               100.64.0.18/32                                all

Mikrotik IPSec Policy (Detailed):

[admin@MikroTik] > /ip ipsec policy print detail
Flags: T - template, B - backup, X - disabled, D - dynamic, I - invalid, A - active, * - default
 0 T X* group=default src-address=::/0 dst-address=::/0 protocol=all proposal=default template=yes

 1   XI  ;;; Azure Strongswan VM + Firewall
        peer=azure-dev-firewall tunnel=yes src-address=192.168.88.0/24 src-port=any dst-address=10.1.0.0/16
        dst-port=any protocol=all action=encrypt level=require ipsec-protocols=esp sa-src-address=<MIKROTIK>
        sa-dst-address=<AZURE FIREWALL> proposal=azure-dev-strongswan ph2-count=0

 2   A  ;;; Azure Strongswan VM + Firewall + SNAT
        peer=azure-dev-firewall tunnel=yes src-address=192.168.88.0/24 src-port=any dst-address=100.64.0.18/32
        dst-port=any protocol=all action=encrypt level=require ipsec-protocols=esp sa-src-address=<MIKROTIK>
        sa-dst-address=<AZURE FIREWALL> proposal=azure-dev-strongswan ph2-count=1

Establish your IPSec connection:

# ipsec restart
# ipsec up azure-to-onprem
# ipsec statusall
  • ip xfrm state:

If ipsec statusall shows the tunnel is up and installed, StrongSwan should already install a policy-based route for 198.18.170.151.

Linux IPsec kernel-level policy shows what traffic should be encrypted (src/dst match, direction).

# ip xfrm policy
src 100.64.0.18/32 dst 198.18.170.151/32
	dir out priority 367231
	tmpl src 10.1.1.66 dst <MIKROTIK PUBLIC IP>
		proto esp spi 0x0e8ba0aa reqid 1 mode tunnel
src 198.18.170.151/32 dst 100.64.0.18/32
	dir fwd priority 367231
	tmpl src <MIKROTIK PUBLIC IP> dst 10.1.1.66
		proto esp reqid 1 mode tunnel
src 198.18.170.151/32 dst 100.64.0.18/32
	dir in priority 367231
	tmpl src <MIKROTIK PUBLIC IP> dst 10.1.1.66
		proto esp reqid 1 mode tunnel
src 0.0.0.0/0 dst 0.0.0.0/0
	socket in priority 0
src 0.0.0.0/0 dst 0.0.0.0/0
	socket out priority 0
src 0.0.0.0/0 dst 0.0.0.0/0
	socket in priority 0
src 0.0.0.0/0 dst 0.0.0.0/0
	socket out priority 0
src ::/0 dst ::/0
	socket in priority 0
src ::/0 dst ::/0
	socket out priority 0
src ::/0 dst ::/0
	socket in priority 0
src ::/0 dst ::/0
	socket out priority 0

SNAT is working as intended. Your policy says:

src 100.64.0.18/32 dst 198.18.170.151/32 dir out
  tmpl src 10.1.1.66 dst <MIKROTIK PUBLIC IP>

This means that traffic from 100.64.0.18 (SNATed source) to 198.18.170.151 is tunneled from 10.1.1.66 (your VM) → <MIKROTIK PUBLIC IP> (the on-prem public IP). So the xfrm policy is installed, and StrongSwan is doing what it should.

Linux IPsec kernel-level state shows active Security Associations (SAs) — i.e., encryption/auth settings and tunnel endpoints.

# ip xfrm state
src 10.1.1.66 dst <MIKROTIK PUBLIC IP>
	proto esp spi 0x0e8ba0aa reqid 1 mode tunnel
	replay-window 0 flag af-unspec
	auth-trunc hmac(sha256) 0xdf9329eb305f22e403c9fc5c4b94488206398505f6d274ddbbdec8b844611a8f 128
	enc cbc(aes) 0x59d4e0109dfa4a81110572984b3adc9c272c256e142e42c869888cd920259c30
	anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000
src <MIKROTIK PUBLIC IP> dst 10.1.1.66
	proto esp spi 0xc08ea295 reqid 1 mode tunnel
	replay-window 32 flag af-unspec
	auth-trunc hmac(sha256) 0xf627f165060d7e5382f8520cba0e45b397f99105c733a866bfbaab7de6a183da 128
	enc cbc(aes) 0xb708741b7c8e1c4e4b6b770368cdbb0c3a9efc8345b922f7933d45ace2ddd28b
	anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000

Linux does not create a separate ipsec0 interface for StrongSwan tunnels — IPsec in Linux is handled by the XFRM framework, which works at the kernel level and transparently intercepts traffic based on policy.

Ensure SNAT is being hit by running nc -zv 198.18.170.151 5672 and iptables in parallel terminal sessions:

# iptables -t nat -L POSTROUTING -n -v --line-numbers
num   pkts bytes target     prot opt in     out     source               destination
1      178 10672 SNAT       all  --  *      *       10.1.1.66            198.18.170.151       to:100.64.0.18

You will see packets counter (pkts) value changed:
```bash
# iptables -t nat -L POSTROUTING -n -v --line-numbers
num   pkts bytes target     prot opt in     out     source               destination
1      195 11692 SNAT       all  --  *      *       10.1.1.66            198.18.170.151       to:100.64.0.18

That means your VM is generating traffic to 198.18.170.151. The traffic is correctly SNATed to 100.64.0.18. StrongSwan should pick it up via the XFRM policy and encrypt it into ESP.

If you still see no ESP/UDP traffic on eth0, even while SNAT counters increase → the XFRM policy is silently dropping packets.

The nft is used to display the full active nftables firewall configuration on a Linux system.

# nft list ruleset
table ip security {
	chain OUTPUT {
		type filter hook output priority 150; policy accept;
		meta l4proto tcp ip daddr 168.63.129.16 tcp dport 53 counter packets 0 bytes 0 accept
		meta l4proto tcp ip daddr 168.63.129.16 skuid 0 counter packets 85410 bytes 42838449 accept
		meta l4proto tcp ip daddr 168.63.129.16 ct state invalid,new counter packets 0 bytes 0 drop
	}
}
table ip nat {
	chain POSTROUTING {
		type nat hook postrouting priority srcnat; policy accept;
		ip saddr 10.1.1.66 ip daddr 198.18.170.151 counter packets 215 bytes 13012 snat to 100.64.0.18
	}
}

Key difference between Stronswan IPSec from Linux VM and Azure VPN Gateway connections is that Azure needs IPSec ESP 50 protocol. So there must be additional Mikrotik Firewall input chain rule for that.

Now you can ping 192.168.88.1 but still can not access port 80 on this address. Why? Because you have now input chain for 10.1.0.0/16 -> 192.168.88.1:80 on your Mikrotik Firewall.

Enable DNAT on On-Premise Side

Create DNAT Rule on Mikrotik Firewall:

[admin@MikroTik] > /ip firewall nat print where chain=dstnat
Flags: X - disabled, I - invalid, D - dynamic
 0    ;;; Farel Dev Strongswan VM
      chain=dstnat action=dst-nat to-addresses=192.168.88.1 to-ports=80 protocol=tcp src-address=100.64.0.18 dst-address=203.0.113.10 dst-port=8080
      log=no log-prefix=""
  • 192.168.88.1 is a MikroTik private IP address in LAN.
  • 100.64.0.18 is a Azure VM SNAT IP address (IPTables postrouting).
  • 203.0.113.10 is a Mikrotik DNAT address.

When you are using SNAT & DNAT the IPSec peer must have 2 policies enabled. Without rule 2 the reply is going directly to 100.64.0.18, bypassing the IPsec tunnel. The reply path is not encrypted, no matching outbound IPsec policy from 192.168.88.1 → 100.64.0.18. As a result, your Azure VM never gets the HTTP payload when you are trying to curl -Lv 203.0.113.10:8080, curl hangs because the response is lost.

 1      ;;; Azure Strongswan VM + Firewall + SNAT + DNAT (Establishing tunnel)
        far.. yes 203.0.113.0/24                                 100.64.0.18/32                                 all        encrypt require          0
 2      ;;; Azure Strongswan VM + Firewall + SNAT + DNAT (Additional policy for DNAT)
        far.. yes 192.168.88.1/32                                100.64.0.18/32                                 all        encrypt require          0

Inbound traffic to 203.0.113.10:8080 hits policy 1 → encrypted → DNATs → 192.168.88.1:80. Reply traffic from MikroTik 192.168.88.1 hits policy 2 → encrypted → back to 100.64.0.18.

Configure input chain for 100.64.0.18 SNAT IP address from Azure VNet to 192.168.88.1 DNAT destination IP and description port.

      ;;; Farel IPSec IKE and NAT-T - Strongswan VM Allow HTTP from Azure
      chain=input action=accept protocol=tcp src-address=100.64.0.18 dst-address=192.168.88.1 dst-port=80 log=no log-prefix=""

I think this part significantly changes the way newcomers view configuration of SNAT/DNAT IPSec connections.

Strongswan IPSec connection on Linux VM in Azure VNet:

conn azure-to-ilyas-homenetwork
  keyexchange=ikev2
  auto=start
  type=tunnel
  authby=psk

  left=%defaultroute              # This VM
  leftid=???                      # Azure Firewall Public IP
  leftsubnet=100.64.0.18/32       # With SNAT using Iptables Postrouting

  # Ilyas' Home Network
  right=???                       # Mikrotik Public IP
  rightsubnet=203.0.113.0/24      # With DNAT 203.0.113.10/32 -> 192.168.88.1/32 on Mikrotik

  ike=aes256-sha256-ecp384
  esp=aes256-sha256

  ikelifetime=30m                 # 1800 seconds
  lifetime=30m                    # 1800 seconds
  rekeymargin=3m                  # Start rekey 3 minutes before expiry (safe default)
  rekeyfuzz=10%

  dpdaction=restart
  dpddelay=10s
  dpdtimeout=30s

  keylife=3600s

IPSec connection status:

azure-to-ilyas-homenetwork[2]: ESTABLISHED 14 minutes ago, 10.1.1.66[<AZURE FIREWALL PUBLIC IP>]...145.249.252.49[<MIKROTIK PUBLIC IP>]
azure-to-ilyas-homenetwork{2}:  INSTALLED, TUNNEL, reqid 2, ESP in UDP SPIs: caf648c9_i 063ee2b2_o
azure-to-ilyas-homenetwork{2}:   100.64.0.18/32 === 203.0.113.0/24

Add Iptables NAT Postrouting rule:

iptables -t nat -A POSTROUTING -s 10.1.1.66/32 -d 203.0.113.10/32 -j SNAT --to-source 100.64.0.18
iptables -t nat -L POSTROUTING -n -v --line-numbers
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1      108  6552 SNAT       all  --  *      *       10.1.1.66            203.0.113.10         to:100.64.0.18

Enjoy using SNAT and DNAT.

Mikrotik advanced logging

Always backup your Mikrotik configuration! Changing IPSec policy can break your network!

[admin@MikroTik] > /system logging print
Flags: X - disabled, I - invalid, * - default
 #    TOPICS                                             ACTION                                             PREFIX
 0  * info                                               memory
 1  * error                                              memory
 2  * warning                                            memory
 3  * critical                                           echo
[admin@MikroTik] > /system logging add topics=ipsec,!packet action=memory
[admin@MikroTik] > /system logging print
Flags: X - disabled, I - invalid, * - default
 #    TOPICS                                             ACTION                                             PREFIX
 0  * info                                               memory
 1  * error                                              memory
 2  * warning                                            memory
 3  * critical                                           echo
 4    ipsec                                              memory
      !packet
[admin@MikroTik] > /log print where topics~"ipsec"
[admin@MikroTik] > /system logging remove 4

Point-to-Site VPN Clients

If you want users to connect your Azure Vnet resources using Virtual Network Gateway you should add this block:

resource "azurerm_virtual_network_gateway" "vpn_gw" {

...

  vpn_client_configuration {
    address_space = ["172.16.201.0/24"]

    root_certificate {
      name = "root-cert"
      public_cert_data = filebase64("certs/azure-vpn-cert.cer")
    }

    vpn_client_protocols = ["OpenVPN", "IkeV2"]
  }
}

You must generate a root certificate:

$ openssl req -x509 -newkey rsa:2048 -keyout P2SRootKey.pem -out P2SRootCert.pem -days 365 -nodes -subj "/CN=AzureP2SRoot"

To export a certificate in DER format for use in Azure, you typically need a .cer file encoded in DER (binary) rather than PEM (Base64). This is often required when uploading a certificate to Azure services like Application Gateway, App Service, or Key Vault (for public keys).

Export as DER for Azure:

$ openssl x509 -outform der -in P2SRootCert.pem -out azure-vpn-cert.cer

Create client cert signed by root:

$ openssl genrsa -out P2SClientKey.pem 2048
$ openssl req -new -key P2SClientKey.pem -out P2SClientReq.csr -subj "/CN=AzureP2SClient"
$ openssl x509 -req -in P2SClientReq.csr -CA P2SRootCert.pem -CAkey P2SRootKey.pem -CAcreateserial -out P2SClientCert.pem -days 365

Connect to Azure Point-to-Site VPN using certificate authentication and OpenVPN

$ az network vnet-gateway vpn-client generate \
  --resource-group <your_rg> \
  --name <your_gateway_name> \
  --authentication-method EAPTLS \
  --processor-architecture Amd64 \
  --output zip \
  --file vpn-profile.zip

Install OpenVPN:

$ sudo apt update
$ sudo apt install openvpn unzip -y

Unpack the profile:

$ mkdir ~/azurevpn
$ unzip vpn-profile.zip -d ~/azurevpn
$ cd ~/azurevpn

DER Certificate Format

From PEM to DER. If you already have a PEM-formatted certificate (.pem, .crt, or .cer with Base64 encoding):

$ openssl x509 -in certificate.pem -outform der -out certificate.der

This will produce a binary DER-encoded certificate (certificate.der), which you can rename to certificate.cer if Azure expects a .cer extension.

From PFX to DER. If you have a .pfx (PKCS#12) file and want to extract just the certificate:

openssl pkcs12 -in certificate.pfx -clcerts -nokeys -out certificate.pem
openssl x509 -in certificate.pem -outform der -out certificate.cer

The first command extracts the certificate in PEM. The second converts it to DER (binary .cer).

Questions

What is IKE Phase 1?

IKE (Internet Key Exchange) Phase 1 is the first step in establishing a secure IPsec VPN tunnel. Its goal is to: Authenticate both VPN peers. Negotiate a shared secret (encryption keys). Create a secure and encrypted channel (ISAKMP SA) to use in Phase 2 (which negotiates actual IPsec settings for data traffic).

What is Diffie-Hellman (DH) in IKE?

Diffie-Hellman (DH) is a key exchange algorithm used during IKE Phase 1 to securely derive a shared secret key over an untrusted network without transmitting the actual key. The DH Group defines the strength of the algorithm — bigger group numbers mean stronger (but slower) cryptographic operations.

Dellie-Hellman (DH) Group 14 vs Group 15 – What's the Difference?

GroupKey TypeKey SizeStrengthPerformanceCurve Type
14Modular Exponentiation2048 bitsStrongFasterClassic DH
15Modular Exponentiation3072 bitsVery StrongSlowerClassic DH

Group 14 – 2048-bit key is strong enough for most production-grade VPNs. Widely supported (Azure, Cisco, Mikrotik, etc.). Balanced between security and performance. Use Group 14 for compatibility and speed, while still maintaining strong security.

Group 15 – 3072-bit key is more secure due to longer key size. Slightly more CPU-intensive (slower negotiation). Might not be supported by all legacy VPN devices. Use Group 15 if both sides support it and performance is acceptable.

Never use older/smaller groups (like Group 1 or Group 5) — they are no longer secure.

DH Groups Supported in Azure VPN Gateway:

Group NameAzure VPN Gateway Support
Group 14 (DHGroup2048)Supported
Group 15Not Supported
Group 20 (ECP384)Supported
Group 2Legacy Support (not recommended)

More information: RFC3526, RFC5114

PFS Group Mapping

TerraformMeaningKey SizeStandard Group
PFS1Diffie-Hellman Group 1768-bitGroup 1
PFS2Diffie-Hellman Group 21024-bitGroup 2
PFS14Diffie-Hellman Group 142048-bitGroup 14
PFS2048MODP 2048-bit (RFC 3526)2048-bitGroup 14 (same as PFS14)
PFS24MODP-2048-256 (RFC 5114)2048-bit (RFC 5114)Group 24
ECP256Elliptic Curve Group 19256-bitGroup 19
ECP384Elliptic Curve Group 20384-bitGroup 20
PFSMMMicrosoft proprietary(custom)N/A
NoneNo Perfect Forward SecrecyNot secureN/A

What is GRE over IPsec?

GRE over IPsec is a hybrid VPN tunneling method that combines GRE (Generic Routing Encapsulation) - a lightweight, flexible tunnel protocol. And IPsec (Internet Protocol Security) - provides strong encryption, integrity, and authentication.

[ Host A ]  <--->  [GRE Tunnel over IPsec]  <--->  [ Host B ]

GRE Tunnel encapsulates inner traffic (any IP, multicast, routing protocols) the original payload (any protocol). Then the GRE packet is wrapped inside an IPsec tunnel (ESP, tunnel mode). The IPsec layer encrypts and authenticates the GRE packet. Result: secure + routable + flexible tunnel

GRE allows you to run protocols like OSPF, BGP, EIGRP (dynamic routing). GRE can carry multicast/broadcast, which IPsec alone cannot. GRE has no built-in security; pairing it with IPsec fixes that.

Original Packet (e.g., ICMP, TCP, BGP)
   ↓
GRE Encapsulation
   ↓
IPsec Encapsulation (ESP in Tunnel Mode)
   ↓
Encrypted GRE packet transmitted over the internet

Azure VPN Gateway and Azure Firewall have no GRE support. Set up a Linux VM in Azure to terminate GRE and IPsec (Azure VPN Gateway does not support GRE natively). Establish an IPsec tunnel using strongSwan or Libreswan. Create a GRE interface on top of the IPsec connection. Route your internal traffic via GRE IPs securely tunneled through IPsec.

Required Ports for IPsec/IKEv2

ProtocolPort(s)Description
UDP500IKEv2 Phase 1
UDP4500NAT-T (when behind NAT)
ESPIP Proto 50IPsec Phase 2 (ESP tunnel)

Important ESP Limitation in Azure Firewall: Azure Firewall does not support IP protocol 50 (ESP) in the network rule collection — only TCP/UDP traffic.

resource "azurerm_route_table" "firewall-rt" {
  name                = "firewall-rt"
  location            = data.azurerm_resource_group.main_rg.location
  resource_group_name = data.azurerm_resource_group.main_rg.name

  ...

  route {
    name                = "bypass-firewall-for-esp-protocol"
    address_prefix      = "145.249.252.49/32" # Router Public IP (Mikrotik)
    next_hop_type       = "Internet"
  }

IPSec Basics

IPSec IKE Policy

Virtual Networks & GRE Support

About cryptographic requirements and Azure VPN gateways

Linux Routing

Address Overlaping Solution

Azure Virtual Network UDR

Azure Training: On-Premises Network Connection

Azure Support

Note: NAT limitations and considerations

  • NAT is supported on the following SKUs: VpnGw25, VpnGw2AZ5AZ.
  • NAT is supported for IPsec/IKE cross-premises connections only. VNet-to-VNet connections or P2S connections aren't supported.
  • NAT rules aren't supported on connections that have Use Policy Based Traffic Selectors enabled.
  • The maximum supported external mapping subnet size for Dynamic NAT is /26.
  • Port mappings can be configured with Static NAT types only. Dynamic NAT scenarios aren't applicable for port mappings.
  • Port mappings can't take ranges at this time. Individual port needs to be entered.
  • Port mappings can be used for both TCP and UDP protocols.
  • Please find below link for your reference.

If Port mappings can be configured with Static NAT types only. Dynamic NAT scenarios aren't applicable for port mappings.

If you continue to experience issues, it may be beneficial to check whether the on-premises VPN device is validated and correctly configured, as this can also impact the stability of the VPN connection.

Alternatives

MethodNotes
Site-to-Site IPsec VPNBest for production, scalable.
Point-to-site VPNNot ideal for Pods, manual mgmt.
Application-layer proxyLess secure, breaks transparency.
Private Link (if in Azure)Not applicable to on-prem.

April 20, 2025