Migrate From AWS Security Groups to Cloud Firewalls

Traducciones al Español
Estamos traduciendo nuestros guías y tutoriales al Español. Es posible que usted esté viendo una traducción generada automáticamente. Estamos trabajando con traductores profesionales para verificar las traducciones de nuestro sitio web. Este proyecto es un trabajo en curso.
Create a Linode account to try this guide with a $ credit.
This credit will be applied to any valid services used during your first  days.

Amazon Web Services (AWS) Security Groups are virtual firewalls that control inbound and outbound traffic to AWS resources like EC2 instances. They operate at the instance level and allow administrators to define traffic rules based on IP addresses, protocols, and ports.

Cloud Firewalls on Akamai Cloud is a network-level firewall service that lets users control traffic for Linode instances and NodeBalancers, Akamai Cloud’s load balancing service. Cloud Firewalls support inbound and outbound traffic management for Linode instances and inbound traffic for NodeBalancers. They operate at Layers 3 and 4 of the OSI model (see diagram below), providing IP, protocol, and port filtering.

This guide includes steps on how to migrate a basic security setup from AWS Security Groups to Cloud Firewalls. It covers planning, documenting your configuration, creating equivalent rules with Cloud Firewalls, and testing results to ensure network security after migration.

Feature Comparison

Before beginning the migration process between service providers, it’s important to understand the capabilities and specifications of both AWS Security Groups and Cloud Firewalls. This can help optimize your results by identifying which rules can be migrated directly and which may require additional configuration.

Features of AWS Security Groups

AWS Security Groups allow you to create sets of firewall rules that control traffic based on IP addresses, CIDR blocks, ports, and protocols. Security groups are stateful (i.e. return traffic is automatically allowed) and attach directly to the network interfaces of your AWS resources.

Features of Cloud Firewalls

Cloud Firewalls on Akamai Cloud is a Layer 3/4 stateless packet filter designed for simplicity and performance. It allows users to specify rules that allow or deny traffic based on source IP, destination port, and protocol (e.g. TCP, UDP, ICMP, and IPEncap). The Cloud Firewalls service does not inspect application-layer traffic; however, it is effective at managing access to services based on IP and port-level rules.

What’s Not Directly Portable

Since Cloud Firewalls doesn’t currently support Layer 7 inspection, features such as pattern matching, geographic filtering, and rate limiting cannot be replicated natively. These must be implemented at the application level using reverse proxies like NGINX or other third-party services.

Before You Begin

Complete the following prerequisites prior to following the steps in this guide.

  1. Follow our Get Started guide to create an Akamai Cloud account if you do not already have one.

  2. Create a personal access token using the instructions in our Manage personal access tokens guide.

  3. Install the Linode CLI using the instructions in the Install and configure the CLI guide. See our API reference for comprehensive documentation of Linode CLI functionality.

  4. You need an AWS account with a user or role that has permission to list, view, and modify EC2 networking settings and Security Groups.

  5. Ensure the version 2 of the AWS CLI is installed locally and configured (via aws configure) for the appropriate credentials and default region.

Example Environment Used in This Guide

The example used throughout this guide involves an AWS Security Group associated with a single Amazon EC2 instance. The EC2 is configured for multiple services:

  • Web traffic handled by NGINX on ports 80 and 443
  • PostgreSQL database on port 5432
  • SSH on port 22
  • Redis on port 6379

The AWS Security Group is configured with inbound rules to restrict access to known IP addresses.

The equivalent setup on Akamai Cloud uses a single Linode instance running the same services. The Cloud Firewalls service is used to recreate the access controls previously handled by the AWS Security Group.

Document Your Current Configuration

Before making changes, it’s essential to fully understand your existing AWS EC2 and Security Group configuration. Document and template how traffic flows to your EC2 instance by noting which ports are open and which services are bound to each port. This can help you set up equivalent access controls using Cloud Firewalls.

Review AWS Security Group Rules

Use the AWS Console or aws CLI to export or list your active Security Group rules.

  1. In the AWS Console, navigate to the EC2 service.

  2. Select the appropriate EC2 instance to view its details.

  3. Under the Security tab, select the Security Group associated with the EC2 instance to view a list of inbound rules for the Security Group:

To access this information using the aws CLI, run the following commands:

  1. Query for security group(s) associated with the EC2 instance, replacing AWS_REGION and EC2_INSTANCE_ID with your values:

    aws ec2 describe-instances \
        --region AWS_REGION \
        --instance-ids EC2_INSTANCE_ID \
        --query "Reservations[0].Instances[0].SecurityGroups"
    [
        {
            "GroupName": "launch-wizard-1",
            "GroupId": "sg-046f0337540471bd1"
        }
    ]
  2. Query for all associated rules for each security group, replacing SECURITY_GROUP_ID (e.g. sg-046f0337540471bd1):

    aws ec2 describe-security-group-rules \
        --region AWS_REGION \
        --filters Name=group-id,Values=SECURITY_GROUP_ID

    This command lists all inbound and outbound rules for the security group. It shows exactly which traffic is allowed on each port of your EC2 instance.

    {
        "SecurityGroupRules": [
            {
                "SecurityGroupRuleId": "sgr-09de63fe55f86984c",
                "GroupId": "sg-046f0337540471bd1",
                "IsEgress": false,
                "IpProtocol": "tcp",
                "FromPort": 22,
                "ToPort": 22,
                "CidrIpv4": "0.0.0.0/0",
                "Description": "Anywhere",
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": "SSH"
                    }
                ]
            },
            {
                "SecurityGroupRuleId": "sgr-05e5d6e0ee20b4ced",
                "GroupId": "sg-046f0337540471bd1",
                "IsEgress": true,
                "IpProtocol": "-1",
                "FromPort": -1,
                "ToPort": -1,
                "CidrIpv4": "0.0.0.0/0",
                "Tags": []
            },
            {
                "SecurityGroupRuleId": "sgr-0cee9d70f10153c73",
                "GroupId": "sg-046f0337540471bd1",
                "IsEgress": false,
                "IpProtocol": "tcp",
                "FromPort": 5432,
                "ToPort": 5432,
                "CidrIpv4": "50.116.12.84/32",
                "Description": "Postgres access for admin server",
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": "Postgres"
                    }
                ]
            },
            {
                "SecurityGroupRuleId": "sgr-033f9a4a8d0c2c7f1",
                "GroupId": "sg-046f0337540471bd1",
                "IsEgress": false,
                "IpProtocol": "tcp",
                "FromPort": 6379,
                "ToPort": 6379,
                "CidrIpv4": "50.116.12.84/32",
                "Description": "Redis access for admin server",
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": "Redis"
                    }
                ]
            },
            {
                "SecurityGroupRuleId": "sgr-010dd8ce746ddf1e6",
                "GroupId": "sg-046f0337540471bd1",
                "IsEgress": false,
                "IpProtocol": "tcp",
                "FromPort": 6379,
                "ToPort": 6379,
                "CidrIpv4": "173.230.145.119/32",
                "Description": "Redis access for external service",
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": "Redis"
                    }
                ]
            },
            {
                "SecurityGroupRuleId": "sgr-0ade40a7b507e4f6a",
                "GroupId": "sg-046f0337540471bd1",
                "IsEgress": false,
                "IpProtocol": "tcp",
                "FromPort": 80,
                "ToPort": 80,
                "CidrIpv4": "0.0.0.0/0",
                "Description": "Anywhere",
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": "HTTP Web"
                    }
                ]
            },
            {
                "SecurityGroupRuleId": "sgr-0d4de7c5f03e750e7",
                "GroupId": "sg-046f0337540471bd1",
                "IsEgress": false,
                "IpProtocol": "tcp",
                "FromPort": 443,
                "ToPort": 443,
                "CidrIpv4": "0.0.0.0/0",
                "Description": "Anywhere",
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": "HTTPS Web"
                    }
                ]
            }
        ]
    }

The example in this guide only has inbound rules, with traffic allowed for specific IP addresses. The inbound permissions for the example in this guide are diagrammed below:

Note Your Own Firewall Configuration
Your firewall may have both inbound and outbound rules, with traffic allowed or denied for specific IP addresses.

Plan Your Rule-Mapping Strategy

After documenting your AWS configuration, plan how to translate those rules using the Cloud Firewalls syntax and feature set.

In this example, core services are exposed on ports 22, 80, 443, 5432, and 6379. The AWS Security Group allows access to certain ports (5432 and 6379) only from an approved IP allowlist, while traffic from any source can reach ports 22, 80, 443. These rules must be recreated on Akamai Cloud to maintain equivalent protection.

It can be helpful to create a side-by-side comparison, mapping AWS Security Group rules to their Cloud Firewalls equivalents. For example, a rule that allows PostgreSQL traffic (TCP 5432) from a specific IP should be represented as a Cloud Firewalls rule allowing TCP traffic on port 5432 from that same IP.

Back up Your Existing Configuration

Before disabling or removing AWS services, create a backup of all relevant configuration data.

Export your existing Security Group configurations by running the following command and saving the output to a file:

aws ec2 describe-security-group-rules \
    --region AWS_REGION \
    --filters Name=group-id,Values=SECURITY_GROUP_ID \
    --output json \
    > sg-rules.json

Create Equivalent Rules with Cloud Firewalls

Once planning and documentation are complete, begin building your new network firewall configuration in Akamai Cloud.

Cloud Firewalls rules can be managed through the Cloud Manager web interface or via the Linode CLI. This section demonstrates both methods.

Enable Cloud Firewalls

  1. While logged into Cloud Manager, navigate to the Firewalls dashboard, and click Create Firewall.

  2. Specify a label for the Cloud Firewall and accept the defaults for the inbound and outbound policies, and click Create Firewall. Initially, you do not need to assign any services. You can focus on rule creation first, then associate services later.

Once the Cloud Firewall has been created, you should see an initially empty list of inbound and outbound firewall rules.

Use the Linode CLI to create a firewall, replacing CLOUD_FIREWALL_LABEL with a label of your choosing (e.g. my-cloud-firewall):

linode-cli firewalls create \
    --rules.inbound_policy DROP \
    --rules.outbound_policy ACCEPT \
    --label "CLOUD_FIREWALL_LABEL"
┌---------┬--------------------┬---------┬---------------------┐
│ id      │ label              │ status  │ created             │
├---------┼--------------------┼---------┼---------------------┤
│ 2420060 │ my-cloud-firewall  │ enabled │ 2025-04-28T17:42:45 │
└---------┴--------------------┴---------┴---------------------┘

Recreate Rules

Recreate each of the rules documented from your AWS Security Group.

  1. Select your Cloud Firewall, and create a new rule by clicking Add An Inbound Rule.

  2. Specify a label and description for the rule. For example:

    Cloud Manager UI for adding an inbound firewall rule.

  3. Next, select the protocol and which ports to apply this rule to. You can select from commonly used ports or select Custom to specify a custom port range. For example:

    Cloud Manager UI for selecting protocol and port range.

  4. For Sources, specify whether you want the rule to apply to all IPv4 or IPv6 addresses, or if you want to provide specific IP addresses. If providing specific IP addresses, add them one at a time. See our guide on managing firewall rules for syntax specifications.

    Cloud Manager UI for entering source IP addresses.

  5. Finally, decide whether the rule is meant to serve as an allowlist (Accept) or denylist (Drop). For this example migration from AWS Security Groups, the action would be Accept. Click Add Rule.

  6. Repeat the steps above to recreate all the equivalent rules from the AWS Security Group configuration.

  7. After adding all rules, click Save Changes.

When using the web UI, rules must be created one at a time. With the Linode CLI, you can add all rules with a single call of the rules-update action for the firewalls command.

  1. First, use nano or text editor of your choice to create a file called inbound-rules.json:

    nano inbound-rules.json

    Enter all the inbound rules as a JSON array, for example:

    File: inbound-rules.json
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    
    [
        {
            "action": "ACCEPT",
            "addresses": {
                "ipv4": [
                    "<span class="placeholder-shortcode">ALLOWED_IP_ADDRESS_1</span>/32",
                    "<span class="placeholder-shortcode">ALLOWED_IP_ADDRESS_2</span>/32"
                ],
                "ipv6": []
            },
            "description": "Redis",
            "label": "restrict",
            "ports": "6379",
            "protocol": "TCP"
        },
        {
            "action": "ACCEPT",
            "addresses": {
                "ipv4": [
                    "<span class="placeholder-shortcode">ALLOWED_IP_ADDRESS_1</span>/32",
                    "<span class="placeholder-shortcode">ALLOWED_IP_ADDRESS_2</span>/32"
                ],
                "ipv6": []
            },
            "description": "PostgreSQL",
            "label": "restrict",
            "ports": "5432",
            "protocol": "TCP"
        },
        {
            "action": "ACCEPT",
            "addresses": {
                "ipv4": [
                    "0.0.0.0/0"
                ],
                "ipv6": []
            },
            "description": "SSH",
            "label": "allow",
            "ports": "22",
            "protocol": "TCP"
        },
        {
            "action": "ACCEPT",
            "addresses": {
                "ipv4": [
                    "0.0.0.0/0"
                ],
                "ipv6": []
            },
            "description": "HTTP web",
            "label": "allow",
            "ports": "80",
            "protocol": "TCP"
        },
        {
            "action": "ACCEPT",
            "addresses": {
                "ipv4": [
                    "0.0.0.0/0"
                ],
                "ipv6": []
            },
            "description": "HTTPS web",
            "label": "allow",
            "ports": "443",
            "protocol": "TCP"
        }
    ]

    When done, press CTRL+X, followed by Y then Enter to save the file and exit nano.

  2. With the file in place, run the following Linode CLI command, making sure to supply your Akamai CLOUD_FIREWALL_ID (e.g 2420060):

    linode-cli firewalls rules-update CLOUD_FIREWALL_ID \
        --inbound "$(cat inbound-rules.json)"
    ┌-------------┬----------------┬-----------------┬---------┐
    │ fingerprint │ inbound_policy │ outbound_policy │ version │
    ├-------------┼----------------┼-----------------┼---------┤
    │ 96379b42    │ DROP           │ ACCEPT          │ 2       │
    └-------------┴----------------┴-----------------┴---------┘
    
    inbound
    ┌--------┬--------------------┬-------------┬---------┬-------┬---------┐
    │ action │ addresses.ipv4     │ description │ label   │ ports │ protocol│
    ├--------┼--------------------┼-------------┼---------┼-------┼---------┤
    │ ACCEPT │ 173.230.145.119/32 │ Redis       │ restrict│ 6379  │ TCP     │
    ├--------┼--------------------┼-------------┼---------┼-------┼---------┤
    │ ACCEPT │ 50.116.12.84/32    │ PostgreSQL  │ restrict│ 5432  │ TCP     │
    ├--------┼--------------------┼-------------┼---------┼-------┼---------┤
    │ ACCEPT │ 0.0.0.0/0          │ SSH         │ allow   │ 22    │ TCP     │
    ├--------┼--------------------┼-------------┼---------┼-------┼---------┤
    │ ACCEPT │ 0.0.0.0/0          │ HTTP web    │ allow   │ 80    │ TCP     │
    ├--------┼--------------------┼-------------┼---------┼-------┼---------┤
    │ ACCEPT │ 0.0.0.0/0          │ HTTPS web   │ allow   │ 443   │ TCP     │
    └--------┴--------------------┴-------------┴---------┴-------┴---------┘
    
    outbound
    ┌--------┬--------------------┬-------------┬---------┬-------┬---------┐
    │ action │ addresses.ipv4     │ description │ label   │ ports │ protocol│
    ├--------┼--------------------┼-------------┼---------┼-------┼---------┤
    └--------┴--------------------┴-------------┴---------┴-------┴---------┘

Attach Instances to the Firewall

You can attach multiple Linodes or NodeBalancers to the Cloud Firewall. Note that inbound and outbound rules apply to Linode instances, whereas only inbound rules apply to NodeBalancers.

See our additional migration documentation for guidance on migrating other services – such as compute instances – to Akamai Cloud.

  1. Navigate to the Linodes tab for your Cloud Firewall and click Add Linodes to Firewall:

  2. From the list, select which Linode (or Linodes) to assign to the Cloud Firewall and click Add:

    List of Linode instances attached to the firewall in the UI.

The firewall rules you specified should now be applied to the Linode (or Linodes) you have added.

  1. To assign Linodes to a Cloud Firewall using the Linode CLI, first retrieve the id of the Linode you want to add:

    linode-cli linodes list
    ┌----------┬-------------┬--------┬---------┬-----------------┐
    │ id       │ label       │ region │ status  │ ipv4            │
    ├----------┼-------------┼--------┼---------┼-----------------┤
    │ 76033001 │ my-linode   │ us-lax │ running │ 172.235.225.120 │
    ├----------┼-------------┼--------┼---------┼-----------------┤
    │ 76033002 │ my-linode-2 │ us-lax │ running │ 172.221.114.36  │
    ├----------┼-------------┼--------┼---------┼-----------------┤
    │ 76033003 │ my-linode-3 │ us-lax │ running │ 172.218.17.4    │
    └----------┴-------------┴--------┴---------┴-----------------┘
  2. Next, execute the device-create action to assign a Linode to the Cloud Firewall, supplying both the LINODE_ID (e.g. 76033001) and the CLOUD_FIREWALL_ID (e.g. 2420060):

    linode-cli firewalls device-create \
        --type linode --id LINODE_ID \
        CLOUD_FIREWALL_ID
    ┌---------┬---------------------┬---------------------┐
    │ id      │ created             │ updated             │
    ├---------┼---------------------┼---------------------┤
    │ 4877449 │ 2025-04-28T18:55:59 │ 2025-04-28T18:55:59 │
    └---------┴---------------------┴---------------------┘

Test and Validate Your Configuration

After applying rules to your Cloud Firewalls, confirm that they behave as expected under real traffic conditions. Note that your firewall configurations may require different testing methods than those listed in this section.

Simulate Expected and Blocked Traffic

From an IP on the allowlist, test access for each service and confirm that the connection succeeds to your instance’s endpoint. Use ssh to test connections from any IP address.

In all examples, replace SERVER_IP_ADDRESS with the IP address of the instance assigned to your Cloud Firewall (e.g. 172.236.228.122).

  1. Use curl to test HTTP traffic through NGINX:

    cURL HTTP Connection Attempt
    curl -I http://SERVER_IP_ADDRESS
    HTTP/1.1 200 OK
    Server: nginx/1.24.0 (Ubuntu)
    Date: Mon, 28 Apr 2025 21:00:32 GMT
    Content-Type: text/html
    Content-Length: 615
    Last-Modified: Mon, 28 Apr 2025 20:58:01 GMT
    Connection: keep-alive
    ETag: "680febd9-267"
    Accept-Ranges: bytes
  2. Similarly, you can also use curl to test HTTPS traffic through NGINX:

    cURL HTTPS Connection Attempt
    curl -I https://SERVER_IP_ADDRESS
    HTTP/1.1 200 OK
    Server: nginx/1.24.0 (Ubuntu)
    Date: Mon, 28 Apr 2025 21:02:02 GMT
    Content-Type: text/html
    Content-Length: 615
    Last-Modified: Mon, 28 Apr 2025 20:58:01 GMT
    Connection: keep-alive
    ETag: "6434bbbe-267"
    Accept-Ranges: bytes
    Note

    If you generated a self-signed certificate, skip certificate verification by adding the -k flag:

    curl -Ik https://SERVER_IP_ADDRESS
  3. Attempt to connect to the PostgreSQL server with the psql client from an allowed IP address, replacing PSQL_PORT (e.g. 5432), PSQL_USERNAME (e.g. testuser), and PSQL_DATABASE (e.g. testdb):

    Successful PostgreSQL Connection Attempt
    psql --host SERVER_IP_ADDRESS \
        --port PSQL_PORT \
        --username PSQL_USERNAME \
        --dbname PSQL_DATABASE \
        --password
    Password: ********
    psql (17.2 (Ubuntu 17.2-1.pgdg20.04+1), server 16.8 (Ubuntu 16.8-0ubuntu0.24.04.1))
    SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off, ALPN: none)
    Type "help" for help.
    
    tesdb=#
  4. In contrast, if you attempt to connect to the PostgreSQL server with the psql client from an IP address not allowed through the Cloud Firewall rules, the execution hangs after prompting for the password:

    Blocked PostgreSQL Connection Attempt
    psql --host SERVER_IP_ADDRESS \
        --port PSQL_PORT \
        --username PSQL_USERNAME \
        --dbname PSQL_DATABASE \
        --password
    Password: ********
  5. Attempt to connect to Redis with redis-cli from an allowed IP address, replacing REDIS_PORT (e.g. 6379):

    Successful Redis Connection Attempt
    redis-cli -h SERVER_IP_ADDRESS -p REDIS_PORT

    The result should be similar to the following:

    172.236.228.122:6379> INFO Server
    # Server
    redis_version:7.0.15
    …
    executable:/usr/bin/redis-server
    config_file:/etc/redis/redis.conf
    io_threads_active:0
  6. Now attempt to connect to Redis with redis-cli from an IP address that is not on the allowlist:

    Blocked Redis Connection Attempt
    redis-cli -h SERVER_IP_ADDRESS -p REDIS_PORT

    The connection attempt simply hangs:

Log and Monitor Behavior

The Cloud Firewalls service does not show per-packet or rule-level logging. To verify behavior, rely on logs from the services themselves. For example:

  • NGINX access logs, as configured in individual virtual server configuration files, are found in /etc/nginx/sites-available.
  • SSH authentication logs are located at /var/log/auth.log.
  • Redis logs are typically found in /var/log/redis/redis-server.log, though this is configurable in /etc/redis/redis.conf.
  • PostgreSQL logs are typically found in /var/log/postgresql/, though this is configurable in /etc/postgresql/[PATH-TO-VERISON]/postgresql.conf.

Connection and activity logs from these services can help to confirm whether traffic is reaching them as expected.

Monitor Post-Migration Performance

Ongoing monitoring helps identify any overlooked configuration issues or unexpected traffic patterns. Continue observing application logs and metrics post-migration. Make sure services are available to intended users and there are no spikes in error rates or timeouts.

If legitimate traffic is being blocked or malicious traffic is being allowed, refine your Cloud Firewalls rules. It may take a few iterations to achieve parity with your original AWS Security Group behavior.

Finalize Your Firewall Migration

Once you’ve validated the new firewall configuration, clean up legacy resources and update internal references:

  • Find components that were connecting with your AWS EC2 instance.
  • Create equivalent rules with Cloud Firewalls to allow traffic from legitimate components.
  • Remove the AWS Security Group.
  • Remove the AWS EC2 instance.

Update runbooks, internal network diagrams, and configuration documentation to reflect the new firewall architecture based on Cloud Firewalls.

More Information

You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

This page was originally published on


Your Feedback Is Important

Let us know if this guide was helpful to you.


Join the conversation.
Read other comments or post your own below. Comments must be respectful, constructive, and relevant to the topic of the guide. Do not post external links or advertisements. Before posting, consider if your comment would be better addressed by contacting our Support team or asking on our Community Site.
The Disqus commenting system for Linode Docs requires the acceptance of Functional Cookies, which allow us to analyze site usage so we can measure and improve performance. To view and create comments for this article, please update your Cookie Preferences on this website and refresh this web page. Please note: You must have JavaScript enabled in your browser.