How to Maintain Persistent AWS EC2 Access with Dynamic Public IP: Solutions for Elastic IP and Security Group Configuration


2 views

When working with AWS EC2 instances, many developers encounter the frustrating scenario where their ISP-assigned public IP changes periodically. This becomes particularly problematic when:

  • Trying to SSH into EC2 instances
  • Maintaining whitelisted IPs in security groups
  • Running automated deployment scripts

Most residential ISPs provide dynamic IP addresses that typically change every 24 hours or after router reboots. The traditional approach of manually updating security groups becomes unsustainable for developers who need:

# Typical security group ingress rule that needs constant updating
{
  "IpProtocol": "tcp",
  "FromPort": 22,
  "ToPort": 22,
  "IpRanges": [{"CidrIp": "123.45.67.89/32"}]
}

For production environments, AWS provides Elastic IP addresses that remain static. However, these come with limitations:

  • Limited to 5 Elastic IPs per region by default
  • Charges apply when not associated with running instances
  • Still requires security group updates if your origin IP changes

For development environments, consider automating security group updates using the AWS CLI:

#!/bin/bash
# Script to update security group with current public IP
MY_IP=$(curl -s https://checkip.amazonaws.com)
AWS_SG_ID="sg-1234567890abcdef0"

aws ec2 authorize-security-group-ingress \
  --group-id $AWS_SG_ID \
  --protocol tcp \
  --port 22 \
  --cidr $MY_IP/32

# Optional: Remove old rules
aws ec2 revoke-security-group-ingress \
  --group-id $AWS_SG_ID \
  --protocol tcp \
  --port 22 \
  --cidr 0.0.0.0/0

For teams or long-term solutions, consider implementing:

  1. A dedicated bastion host with static IP
  2. VPN access to your VPC
  3. AWS Client VPN endpoint

Example CloudFormation snippet for bastion setup:

Resources:
  BastionHost:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: ami-12345678
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          GroupSet:
            - !Ref BastionSecurityGroup
          SubnetId: !Ref PublicSubnet

For advanced users, create a serverless solution that:

const AWS = require('aws-sdk');
const ec2 = new AWS.EC2();

exports.handler = async (event) => {
  const currentIp = event.requestContext.identity.sourceIp;
  
  await ec2.authorizeSecurityGroupIngress({
    GroupId: process.env.SECURITY_GROUP_ID,
    IpPermissions: [{
      IpProtocol: 'tcp',
      FromPort: 22,
      ToPort: 22,
      IpRanges: [{ CidrIp: ${currentIp}/32 }]
    }]
  }).promise();
  
  return { statusCode: 200 };
};

Combine these approaches with SSH config management:

Host my-ec2-instance
  HostName ec2-12-34-56-78.compute-1.amazonaws.com
  User ec2-user
  IdentityFile ~/.ssh/my-key.pem
  ProxyCommand bash -c "aws ec2-instance-connect send-ssh-public-key --instance-id i-1234567890abcdef0 --instance-os-user %r --ssh-public-key file://%d/%i.pub && nc %h %p"

For budget-conscious developers:

  • Use AWS Session Manager (no inbound ports needed)
  • Configure EC2 Instance Connect
  • Leverage temporary credentials

When your ISP assigns dynamic public IPs that rotate every 24 hours, managing AWS EC2 access becomes tedious. Traditional security group configurations require constant updates to whitelist your new IP address. This creates operational overhead and potential service interruptions.

You have three primary approaches to solve this:

  • Elastic IP assignment
  • Security Group CIDR range expansion
  • SSH bastion/jump host setup

Allocate an Elastic IP in your AWS account and associate it with your instance. This gives you a persistent public IP that won't change:

# AWS CLI commands
aws ec2 allocate-address --domain vpc
aws ec2 associate-address --instance-id i-1234567890abcdef0 --public-ip 203.0.113.10

Remember that AWS charges for unassociated Elastic IPs, so only use this when you need permanent persistence.

If your ISP provides IPs within a predictable range, you can whitelist an entire CIDR block:

# Terraform example
resource "aws_security_group" "allow_ssh" {
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["123.45.67.0/24"]  # Your ISP's IP range
  }
}

Use whois or contact your ISP to determine your provider's allocation patterns.

For enterprise environments, implement SSH certificate-based authentication:

# On your CA server
ssh-keygen -t rsa -f host_ca -C host_ca
ssh-keygen -s host_ca -h -I "ec2-host" -n ec2.example.com ec2_host_key.pub

# In sshd_config
TrustedUserCAKeys /etc/ssh/host_ca.pub
HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub

Consider setting up a site-to-site VPN or AWS Client VPN endpoint. This provides secure access without exposing SSH ports:

# Sample OpenVPN client config
client
dev tun
proto udp
remote vpn.example.com 1194
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server

Implement a Lambda function to update security groups when your IP changes:

import boto3
import requests

def lambda_handler(event, context):
    current_ip = requests.get('https://checkip.amazonaws.com').text.strip()
    ec2 = boto3.client('ec2')
    ec2.authorize_security_group_ingress(
        GroupId='sg-12345678',
        IpPermissions=[{
            'IpProtocol': 'tcp',
            'FromPort': 22,
            'ToPort': 22,
            'IpRanges': [{'CidrIp': f'{current_ip}/32'}]}])