Cloud Firewall Configuration: Security Groups and NACLs

Misconfigured cloud firewalls are a common cause of data leaks and service unavailability. We configure Security Groups and NACLs in AWS, GCP, and Azure to close critical vulnerabilities. Our team delivers a turnkey project—from auditing current rules to implementation and ongoing support.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

We regularly encounter projects where misconfigured Security Groups or NACLs lead to data leaks or service unavailability. A common issue is leaving SSH (port 22) open to all IPs after testing. Or forgotten ephemeral ports in NACLs blocking return traffic. Let's look at how to properly organize network security in the cloud. Over the past years, we have audited over 50 cloud infrastructures: in 70% of cases we found critical errors in firewall rules. We'll tell you how to avoid these mistakes.

Why Security Groups Don't Replace NACLs

Security Groups and Network ACLs are different layers of protection in AWS that are often confused. Security Group operates at the ENI (network interface) level, is stateful — return traffic is automatically allowed. NACL operates at the subnet level, is stateless — rules must be written explicitly for both directions. Most issues with "why is it not accessible" in AWS are due to incorrectly configured NACLs. Understanding the difference is critical: according to statistics, 80% of incidents are related to incorrect NACL configuration.

Key differences are summarized in the table:

Parameter Security Group NACL
Level Instance (ENI) Subnet
Stateful Yes No
Deny support No (only allow) Yes
Rule order All rules are evaluated By number (first match)
Typical use Application access control Blocking suspicious IPs, extra barrier

According to AWS documentation, Security Group is the first barrier, NACL is the second. However, in practice many limit themselves to Security Groups only.

How to Automate Rule Management with Terraform?

Using Infrastructure as Code (Terraform) speeds up changes by 3 times compared to manual configuration via the console. Terraform code allows versioning rules and performing code reviews.

Example of a correct Security Group structure:

resource "aws_security_group" "web" {
  name = "web-tier"
  description = "HTTP/HTTPS from internet"
  vpc_id = var.vpc_id

  ingress {
    from_port = 443
    to_port = 443
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_security_group" "app" {
  name = "app-tier"
  description = "Traffic from web tier only"
  vpc_id = var.vpc_id

  ingress {
    from_port = 8080
    to_port = 8080
    protocol = "tcp"
    security_groups = [aws_security_group.web.id] # reference to SG, not CIDR
  }
}

The reference security_groups = [sg-id] instead of CIDR is a key pattern. When scaling the web-tier, new instances automatically gain access to the app-tier without manual rule updates.

NACL: When You Can't Do Without Them

NACLs are needed in two cases: explicit deny for specific IPs (Security Group cannot deny — only allow), and an additional barrier at the subnet level.

Example Terraform code for NACL:

resource "aws_network_acl" "private" {
  vpc_id = var.vpc_id
  subnet_ids = var.private_subnet_ids

  # Explicitly block suspicious IP
  ingress {
    rule_no = 90
    protocol = "-1"
    action = "deny"
    cidr_block = "198.51.100.0/24"
    from_port = 0
    to_port = 0
  }

  # Allow from VPC
  ingress {
    rule_no = 100
    protocol = "tcp"
    action = "allow"
    cidr_block = var.vpc_cidr
    from_port = 0
    to_port = 65535
  }

  # Ephemeral ports for stateless NACL (mandatory!)
  egress {
    rule_no = 100
    protocol = "tcp"
    action = "allow"
    cidr_block = "0.0.0.0/0"
    from_port = 1024
    to_port = 65535
  }
}
Common mistake: ephemeral ports Ephemeral ports (1024–65535) in egress NACL rules are what is most often forgotten. Return traffic for TCP requests comes from ephemeral ports. Without this rule, the NACL blocks responses, even if the Security Group allows everything normally.

Comparison of Cloud Providers

Cloud Main Object Stateful/Stateless Level
AWS Security Group, NACL SG stateful, NACL stateless Instance/Subnet
GCP Firewall Rules Stateful VPC
Azure NSG Stateful Subnet/NIC

In GCP there is no NACL — everything is done through Firewall Rules at the VPC level. An important feature: rules are applied by instance tags or service accounts, not by subnet:

resource "google_compute_firewall" "allow_internal_app" {
  name = "allow-app-from-web"
  network = var.network_name
  allow {
    protocol = "tcp"
    ports = ["8080"]
  }
  source_tags = ["web-tier"]
  target_tags = ["app-tier"]
}

resource "google_compute_firewall" "deny_all_ingress" {
  name = "deny-all-ingress"
  network = var.network_name
  direction = "INGRESS"
  priority = 65534
  deny {
    protocol = "all"
  }
  source_ranges = ["0.0.0.0/0"]
}

In Azure, NSG can be attached to either a subnet or directly to a NIC. Augmentation with service tags (AzureLoadBalancer, VirtualNetwork) simplifies rules:

resource "azurerm_network_security_rule" "allow_lb" {
  name                        = "allow-azure-lb"
  priority                    = 100
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "443"
  source_address_prefix       = "AzureLoadBalancer"
  destination_address_prefix  = "*"
  resource_group_name         = var.rg_name
  network_security_group_name = azurerm_network_security_group.app.name
}

Typical Configuration Mistakes

  • Open port 0–65535 for 0.0.0.0/0 in outbound Security Group — encountered almost everywhere. Theoretically allows any outbound connections from the instance, including data exfiltration. In practice, outbound is restricted only where justified by management complexity.
  • Duplicate rules in NACL with different rule_no — NACL processes rules by number, first match applies. Rule 90 with deny overrides rule 100 with allow for the same CIDR.
  • Using CIDR instead of Security Group references — requires manual updates when scaling.

What's Included in the Work

  • Audit of current rules — identify unused, redundant, and dangerous rules. Provide a detailed report with recommendations.
  • Design of new configuration — use Terraform to declaratively describe all Security Groups and NACLs. All changes go through code review.
  • Documentation — network diagram with all rules, description of exceptions and justifications.
  • Team training — how to maintain rules via IaC, what to do when adding a new microservice.

Work Process

  1. Analytics — collect current network topology, firewall rules, compliance requirements. Identify critical vulnerabilities.
  2. Design — develop target architecture using least privilege principle. Determine which rules can be consolidated, where NACLs are needed.
  3. Implementation — write Terraform code, create pull request, conduct code review. Deploy first in staging environment.
  4. Testing — verify connectivity between services, ensure legitimate traffic is not blocked. Use test scenarios.
  5. Production deployment — apply changes on schedule, monitor errors via CloudWatch / Stackdriver.

Timelines

Audit and configuration of Security Groups for a typical three-tier application (web/app/db) in one cloud — 1-2 days. Multi-cloud or complex microservice architecture with dozens of Security Groups — 3-5 days. Investment in audit and configuration typically pays for itself by preventing incidents: the average cost of a data breach is $4.35 million according to IBM. Cost is calculated individually after analyzing the current architecture.

Over the years, we have completed more than 50 cloud security projects. Certified AWS and Azure engineers ensure your network is secured according to best practices. Get an analysis of your current security rules. Contact us for an audit of your cloud infrastructure. Request a consultation with a DevOps engineer now.