Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions infrastructure/modules/nat-gateway/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# nat-gateway

Deploy an [Azure NAT gateway](https://learn.microsoft.com/en-us/azure/nat-gateway/nat-overview) with a dedicated public IP address. Provides explicit outbound connectivity for subnets whose VMs have no public IP addresses.

## Terraform documentation

For the list of inputs, outputs, resources... check the [terraform module documentation](tfdocs.md).

## Prerequisites

The subnet must already exist. Use the [subnet module](../subnet/) to create it and pass the resulting subnet ID to this module.

> **Note:** Default outbound access for VMs was retired by Microsoft in September 2025. A NAT gateway (or equivalent) is required for any subnet that needs outbound internet connectivity.

## Usage

```hcl
module "nat_gateway" {
source = "../../../dtos-devops-templates/infrastructure/modules/nat-gateway"

name = "nat-myapp-dev-uks"
public_ip_name = "pip-nat-myapp-dev-uks"
resource_group_name = azurerm_resource_group.main.name
location = "uksouth"
subnet_id = module.subnet_servers.id
}
```

## Zones

Azure NAT gateway supports a single availability zone (zonal) or no zone pinning (regional). It does not support zone-redundant deployment across multiple zones. Pass a single zone for a zonal deployment:

```hcl
module "nat_gateway" {
...
zones = ["1"]
}
```

An empty list (the default) deploys the NAT gateway without zone pinning.

## Idle timeout

The TCP idle timeout defaults to 4 minutes. Increase it for workloads with long-lived connections:

```hcl
module "nat_gateway" {
...
idle_timeout_in_minutes = 10
}
```

Valid range is 4–120 minutes.

## Monitoring

NAT gateway does not support Azure Monitor diagnostic settings. Metrics such as SNAT connection count, dropped packets, and throughput are available natively in the Azure portal under the resource's Metrics blade.
33 changes: 33 additions & 0 deletions infrastructure/modules/nat-gateway/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module "pip" {
source = "../public-ip"

name = var.public_ip_name
resource_group_name = var.resource_group_name
location = var.location
allocation_method = "Static"
sku = "Standard"
zones = var.zones

tags = var.tags
}

resource "azurerm_nat_gateway" "nat_gateway" {
name = var.name
location = var.location
resource_group_name = var.resource_group_name
sku_name = "Standard"
idle_timeout_in_minutes = var.idle_timeout_in_minutes
zones = var.zones

tags = var.tags
}

resource "azurerm_nat_gateway_public_ip_association" "nat_gateway_pip" {
nat_gateway_id = azurerm_nat_gateway.nat_gateway.id
public_ip_address_id = module.pip.id
}

resource "azurerm_subnet_nat_gateway_association" "nat_gateway_subnet" {
subnet_id = var.subnet_id
nat_gateway_id = azurerm_nat_gateway.nat_gateway.id
}
14 changes: 14 additions & 0 deletions infrastructure/modules/nat-gateway/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
output "id" {
description = "The ID of the NAT gateway."
value = azurerm_nat_gateway.nat_gateway.id
}

output "name" {
description = "The name of the NAT gateway."
value = azurerm_nat_gateway.nat_gateway.name
}

output "public_ip_address" {
description = "The public IP address associated with the NAT gateway."
value = module.pip.ip_address
}
96 changes: 96 additions & 0 deletions infrastructure/modules/nat-gateway/tfdocs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Module documentation

## Required Inputs

The following input variables are required:

### <a name="input_name"></a> [name](#input\_name)

Description: The name of the NAT gateway.

Type: `string`

### <a name="input_public_ip_name"></a> [public\_ip\_name](#input\_public\_ip\_name)

Description: The name of the public IP address resource created for the NAT gateway.

Type: `string`

### <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name)

Description: The name of the resource group in which to create the NAT gateway.

Type: `string`

### <a name="input_subnet_id"></a> [subnet\_id](#input\_subnet\_id)

Description: The ID of the subnet to associate with the NAT gateway.

Type: `string`

## Optional Inputs

The following input variables are optional (have default values):

### <a name="input_idle_timeout_in_minutes"></a> [idle\_timeout\_in\_minutes](#input\_idle\_timeout\_in\_minutes)

Description: The idle timeout in minutes for the NAT gateway. Must be between 4 and 120.

Type: `number`

Default: `4`

### <a name="input_location"></a> [location](#input\_location)

Description: The location/region where the NAT gateway will be created.

Type: `string`

Default: `"uksouth"`

### <a name="input_tags"></a> [tags](#input\_tags)

Description: A mapping of tags to assign to the resource.

Type: `map(string)`

Default: `{}`

### <a name="input_zones"></a> [zones](#input\_zones)

Description: Availability zones for the NAT gateway and its public IP. Use ["1"] for a zonal deployment. An empty list deploys with no zone redundancy.

Type: `list(string)`

Default: `[]`
## Modules

The following Modules are called:

### <a name="module_pip"></a> [pip](#module\_pip)

Source: ../public-ip

Version:
## Outputs

The following outputs are exported:

### <a name="output_id"></a> [id](#output\_id)

Description: The ID of the NAT gateway.

### <a name="output_name"></a> [name](#output\_name)

Description: The name of the NAT gateway.

### <a name="output_public_ip_address"></a> [public\_ip\_address](#output\_public\_ip\_address)

Description: The public IP address associated with the NAT gateway.
## Resources

The following resources are used by this module:

- [azurerm_nat_gateway.nat_gateway](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway) (resource)
- [azurerm_nat_gateway_public_ip_association.nat_gateway_pip](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway_public_ip_association) (resource)
- [azurerm_subnet_nat_gateway_association.nat_gateway_subnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_nat_gateway_association) (resource)
59 changes: 59 additions & 0 deletions infrastructure/modules/nat-gateway/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
variable "name" {
description = "The name of the NAT gateway."
type = string
validation {
condition = can(regex("^[a-zA-Z0-9][a-zA-Z0-9.-]{0,78}[a-zA-Z0-9]$", var.name))
error_message = "The NAT gateway name must be between 2 and 80 characters, start and end with an alphanumeric character, and can contain alphanumeric characters, hyphens, and periods."
}
}

variable "resource_group_name" {
description = "The name of the resource group in which to create the NAT gateway."
type = string
}

variable "location" {
description = "The location/region where the NAT gateway will be created."
type = string
default = "uksouth"
validation {
condition = contains(["uksouth", "ukwest"], var.location)
error_message = "The location must be either uksouth or ukwest."
}
}

variable "public_ip_name" {
description = "The name of the public IP address resource created for the NAT gateway."
type = string
}

variable "subnet_id" {
description = "The ID of the subnet to associate with the NAT gateway."
type = string
}

variable "idle_timeout_in_minutes" {
description = "The idle timeout in minutes for the NAT gateway. Must be between 4 and 120."
type = number
default = 4
validation {
condition = var.idle_timeout_in_minutes >= 4 && var.idle_timeout_in_minutes <= 120
error_message = "idle_timeout_in_minutes must be between 4 and 120."
}
}

variable "zones" {
description = "Availability zones for the NAT gateway and its public IP. Use [\"1\"] for a zonal deployment. An empty list deploys with no zone redundancy."
type = list(string)
default = []
validation {
condition = length(var.zones) <= 1
error_message = "NAT gateway supports at most one availability zone."
}
}

variable "tags" {
description = "A mapping of tags to assign to the resource."
type = map(string)
default = {}
}
Loading