☁️

AWS

42 notes  •  Cloud Computing

Change Key Pair of an EC2 Instance

Once an EC2 instance is running you cannot change the key pair at the metadata level, but you can update authorized_keys to accept a new SSH public key. Three methods are below.

Method 1 – Update authorized_keys via a temporary instance (EBS)

  1. Stop the target EC2 instance.
  2. Detach its root volume (/dev/sda1) — call it Volume A.
  3. Launch a temporary t3.micro in the same subnet using the new key pair.
  4. Attach Volume A to the temp instance as /dev/xvdf.
  5. SSH in and mount it: sudo mount /dev/xvdf1 /mnt/tmp
  6. Copy the new public key:
    sudo cp ~/.ssh/authorized_keys /mnt/tmp/home/ubuntu/.ssh/authorized_keys
    (replace ubuntu with ec2-user or root as needed)
  7. Unmount: sudo umount /mnt/tmp
  8. Detach Volume A, re-attach to the original instance as /dev/sda1, start it.
  9. Terminate the temporary instance.

Method 2 – Add new public key while you still have access

ssh-keygen -f YOURKEY.pem -y >> ~/.ssh/authorized_keys

Always verify the new key works in a separate terminal before closing the current session.

authorized_keys locations by AMI

  • Amazon Linux / RHEL: /home/ec2-user/.ssh/authorized_keys
  • Ubuntu: /home/ubuntu/.ssh/authorized_keys
  • Root: /root/.ssh/authorized_keys

Method 3 – Save as AMI and relaunch (EBS-backed only)

  1. Create an AMI from the instance (Actions → Image and templates → Create image).
  2. Launch a new instance from that AMI, selecting the new key pair.
  3. Move any Elastic IP to the new instance, terminate the old one.

Increase EBS Volume Size via AWS CLI

Use aws ec2 modify-volume to expand an EBS volume without stopping the instance. You must then also extend the filesystem inside the OS.

Step 1 – Resize the EBS volume

aws ec2 modify-volume --region us-east-1 --volume-id vol-0b6995fd9b566c8b0 --size 1000

Replace the region, volume ID, and size (GB) with your values. Wait until the modification state shows completed.

Step 2 – Extend the partition and filesystem (Linux)

lsblk                          # confirm new size is visible
sudo growpart /dev/xvda 1      # grow the partition
sudo resize2fs /dev/xvda1      # ext4
# For XFS (Amazon Linux 2):
sudo xfs_growfs /

Step 3 – Verify

df -h

Connect Multiple RDS Instances with phpMyAdmin

phpMyAdmin installed via apt-get defaults to localhost MySQL only. This guide configures it to connect to multiple RDS instances simultaneously.

Prerequisites

  • phpMyAdmin installed on an EC2 host
  • RDS Security Group allows TCP 3306 inbound from the phpMyAdmin host's IP or Security Group

Step 1 – Install phpMyAdmin

sudo apt-get install phpmyadmin

Step 2 – Test RDS connectivity

mysql -h <RDS_ENDPOINT> -P 3306 -u <USERNAME> -p

If you get ERROR 2003: Can't connect, check the RDS Security Group inbound rules.

Step 3 – Add RDS server entries to config.inc.php

Edit /etc/phpmyadmin/config.inc.php. After the existing server block, add one block per RDS instance:

$i++;
$cfg['Servers'][$i]['host']         = 'your-rds-endpoint.rds.amazonaws.com';
$cfg['Servers'][$i]['port']         = '3306';
$cfg['Servers'][$i]['connect_type'] = 'tcp';
$cfg['Servers'][$i]['extension']    = 'mysqli';
$cfg['Servers'][$i]['compress']     = TRUE;
$cfg['Servers'][$i]['auth_type']    = 'cookie';

Repeat the block (starting with $i++;) for each additional RDS host, then restart Apache:

sudo systemctl restart apache2

Install and Configure Varnish with Apache on Ubuntu

Varnish Cache sits in front of Apache, serving cached responses to dramatically cut backend load. Apache moves to port 8080 while Varnish listens on port 80.

Step 1 – Install Varnish

sudo curl http://repo.varnish-cache.org/debian/GPG-key.txt | sudo apt-key add -
echo "deb http://repo.varnish-cache.org/ubuntu/ trusty varnish-4.0" | sudo tee -a /etc/apt/sources.list
sudo apt-get update && sudo apt-get install varnish

Step 2 – Move Apache to port 8080

In /etc/apache2/ports.conf change Listen 80Listen 8080.
Update your VirtualHost: <VirtualHost *:8080>, then restart Apache.

Step 3 – Configure Varnish

Edit /etc/default/varnish:

DAEMON_OPTS="-a :80 -T localhost:6082 -f /etc/varnish/default.vcl -s malloc,256m"

Edit /etc/varnish/default.vcl:

backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

Step 4 – Start Varnish and verify

sudo systemctl restart varnish apache2
curl -I http://yourdomain.com   # should show: Via: 1.1 varnish

Configure Varnish Logs, Logrotate and Awstats

When Varnish caches requests, Apache's access log misses them. Use varnishncsa to write Apache-format logs from Varnish, then feed them to logrotate and awstats.

Step 1 – Enable varnishncsa

sudo systemctl enable varnishncsa
sudo systemctl start varnishncsa

Default log: /var/log/varnish/varnishncsa.log

Step 2 – Configure logrotate

Create /etc/logrotate.d/varnishncsa:

/var/log/varnish/varnishncsa.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    postrotate
        systemctl restart varnishncsa
    endscript
}

Step 3 – Point awstats at the Varnish log

Edit /etc/awstats/awstats.yourdomain.conf:

LogFile="/var/log/varnish/varnishncsa.log"
LogFormat="%host %other %logname %time1 %methodurl %code %bytesd %refererquot %uaquot"
sudo /usr/lib/cgi-bin/awstats.pl -config=yourdomain -update

Implementing SSL on Amazon S3 Static Websites

S3 static website hosting does not support HTTPS natively. The recommended solution is CloudFront + ACM (free certificate).

Step 1 – Request an ACM certificate (us-east-1 only)

  1. Open ACM in the us-east-1 region.
  2. Request a public certificate for example.com and www.example.com.
  3. Complete DNS validation.

Step 2 – Create a CloudFront distribution

  1. Origin: your S3 website endpoint (my-bucket.s3-website-us-east-1.amazonaws.com).
  2. Alternate domain names: example.com, www.example.com.
  3. SSL: choose the ACM certificate from Step 1.
  4. Viewer protocol policy: Redirect HTTP to HTTPS.

Step 3 – Update Route 53

Create an Alias A record for your domain pointing to the CloudFront distribution (d1234.cloudfront.net).

Note: Use the S3 website endpoint as the origin (not the REST API endpoint) to preserve index.html routing. CloudFront propagation takes 15–30 minutes.

Upload a Custom SSL Certificate to Amazon CloudFront

If you have a custom SSL certificate (not from ACM), upload it to IAM and attach it to CloudFront.

Step 1 – Upload to IAM (not ACM)

aws iam upload-server-certificate \
  --server-certificate-name my-cert \
  --certificate-body file://certificate.crt \
  --private-key file://private.key \
  --certificate-chain file://chain.crt \
  --path /cloudfront/

The /cloudfront/ path is required for CloudFront to access it.

Step 2 – Attach to CloudFront

  1. CloudFront → select distribution → Edit.
  2. Under SSL CertificateCustom SSL Certificate.
  3. Select the uploaded certificate → Save.

Recommendation

Prefer ACM for new setups — certificates are free, auto-renew, and integrate natively with CloudFront without CLI steps.

HTTP Basic Authentication with Apache on Ubuntu

Apache's mod_auth_basic password-protects directories with a username/password prompt — useful for staging sites or admin panels.

Step 1 – Install apache2-utils

sudo apt-get install apache2-utils

Step 2 – Create a password file

sudo htpasswd -c /etc/apache2/.htpasswd firstuser    # -c creates the file
sudo htpasswd /etc/apache2/.htpasswd anotheruser     # omit -c to append

Step 3 – Configure Apache

<Directory "/var/www/html/protected">
    AuthType Basic
    AuthName "Restricted Area"
    AuthUserFile /etc/apache2/.htpasswd
    Require valid-user
</Directory>
sudo systemctl restart apache2

Step 4 – Test

curl -u firstuser:password http://yourdomain.com/protected/

Create Swap Space in Ubuntu

Swap allows the OS to use disk as overflow memory. Particularly useful on low-RAM EC2 instances (t2/t3.micro).

Recommended swap size

  • ≤ 2 GB RAM → 2× RAM (min 32 MB)
  • 2–32 GB RAM → 4 GB + (RAM − 2 GB)
  • ≥ 32 GB RAM → 1× RAM

Create and enable swap

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
free -h    # verify

Persist across reboots

Add to /etc/fstab:

/swapfile   none   swap   sw   0   0

Tune swappiness (recommended for servers)

sudo sysctl vm.swappiness=10
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf

Forcing HTTPS in Amazon EC2 (behind ELB)

When ELB terminates SSL, all traffic reaches EC2 over HTTP on port 80. Check the X-Forwarded-Proto header (not the port) to detect the original protocol and redirect.

Apache

RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} =http
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Nginx

if ($http_x_forwarded_proto = "http") {
    return 301 https://$host$request_uri;
}

HAProxy

frontend http
    bind *:80
    acl is_http hdr(X-Forwarded-Proto) http
    redirect scheme https if is_http

Do not use SERVER_PORT = 80 as the condition behind an ELB — all backend traffic arrives on 80 regardless of the original protocol.

Force HTTPS Behind an Elastic Load Balancer

ELB adds X-Forwarded-Proto so backends can detect the client's original protocol. Use this header to enforce HTTPS.

Apache

RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Nginx

server {
    listen 80;
    if ($http_x_forwarded_proto != "https") {
        return 301 https://$host$request_uri;
    }
}

ALB native redirect (no server config needed)

  1. EC2 → Load Balancers → select ALB → Listeners tab.
  2. HTTP:80 listener → View/edit rules.
  3. Add rule: IF path * → THEN redirect to HTTPS:443, status 301.

WordPress with Apache, ELB, and SSL Termination

WordPress behind an ELB with SSL termination receives HTTP internally and generates http:// URLs, causing redirect loops. The fix is to detect the forwarded protocol.

Step 1 – wp-config.php (before require_once)

if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
    $_SERVER['HTTPS'] = 'on';
}
define('FORCE_SSL_ADMIN', true);

Step 2 – WordPress URLs

Settings → General: set both WordPress Address and Site Address to https://yourdomain.com.

Step 3 – Apache redirect

RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} =http
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

ELB listener setup

  • HTTPS:443 → forwards to EC2 port 80 (SSL terminated at ELB)
  • HTTP:80 → forwards to EC2 port 80 (Apache handles the redirect)
  • SSL certificate lives on the ELB, not Apache

Troubleshooting redirect loops

If you see a loop, verify $_SERVER['HTTPS'] = 'on' is being set (add var_dump($_SERVER['HTTPS']); temporarily) and that WordPress Site URL starts with https://.

Install the AWS CodeDeploy Agent

The CodeDeploy agent must be running on each EC2 instance that will receive deployments.

Install on Ubuntu

sudo apt-add-repository ppa:brightbox/ruby-ng
sudo apt-get update
sudo apt-get install -y ruby2.3 wget
wget https://aws-codedeploy-eu-west-2.s3.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto

Replace eu-west-2 with your region (e.g., us-east-1).

Start and enable on boot

sudo service codedeploy-agent start
sudo systemctl enable codedeploy-agent

Verify

sudo service codedeploy-agent status

IAM requirement

Attach the AmazonEC2RoleforAWSCodeDeploy managed policy to the instance's IAM role so it can download deployment artefacts from S3.

Running Scripts on EC2 Linux Instances at Launch (User Data)

EC2 User Data runs shell scripts or cloud-init directives automatically on first boot — the standard way to bootstrap instances without manual SSH.

Key facts

  • Runs as root on first boot only (by default).
  • Scripts must start with #!/bin/bash.
  • Output is logged to /var/log/cloud-init-output.log.

Example – install and start Apache on first launch

#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from $(hostname)</h1>" > /var/www/html/index.html

Add User Data during instance launch

  1. In the launch wizard, expand Advanced details.
  2. Paste the script in the User data field.

Edit User Data on a stopped instance

  1. Stop the instance.
  2. Actions → Instance settings → Edit user data.
  3. Paste the new script, save, then start the instance.

Debug

sudo cat /var/log/cloud-init-output.log

VPC Best Configuration Practices

A well-designed VPC isolates public-facing and private resources, controls traffic, and provides defence-in-depth.

Recommended subnet layout

  • Public subnets: Load balancers, NAT Gateways, bastion hosts.
  • Private subnets: Application servers, databases. No direct inbound internet access.
  • Spread across at least two Availability Zones for HA.

Core components

  • Internet Gateway — public subnet internet access.
  • NAT Gateway — outbound-only internet for private subnets.
  • Security Groups — stateful, instance-level firewall.
  • Network ACLs — stateless, subnet-level additional layer.
  • VPC Flow Logs — network traffic audit trail.

Best practices

  • Use /16 CIDR for the VPC, /24 per subnet.
  • Never place RDS in a public subnet.
  • Use SSM Session Manager instead of exposing SSH (port 22) to the internet.
  • Use VPC Endpoints for S3 and DynamoDB to avoid public-internet routing.

Bastion host SSH jump (if SSH is needed)

ssh -i key.pem -J ec2-user@BASTION_PUBLIC_IP ec2-user@PRIVATE_INSTANCE_IP

Accessing EC2 Instances in a Private Subnet

Instances in private subnets have no direct internet access. Use one of these methods to reach them.

Option 1 – Bastion host (jump server)

Launch a small instance in a public subnet and SSH through it:

ssh -i key.pem -J ec2-user@BASTION_PUBLIC_IP ec2-user@PRIVATE_IP

Option 2 – AWS Systems Manager Session Manager (recommended)

No SSH port or key pair required — SSM creates an outbound HTTPS tunnel.

  1. Attach the AmazonSSMManagedInstanceCore policy to the instance's IAM role.
  2. Systems Manager → Session Manager → Start session → select instance.

Option 3 – AWS VPN / Direct Connect

For enterprise setups, attach a VPN Gateway to the VPC and route from your on-premises network. Private instances then become directly reachable over the VPN.

Note on Elastic IPs and private subnets

Attaching an EIP to an instance in a private subnet does not make it internet-accessible. The subnet's route table must also point 0.0.0.0/0 to an Internet Gateway — which makes it a public subnet by definition.

Monitoring Redis in Real Time

Redis exposes built-in commands to inspect memory, clients, throughput, and keyspace — useful for understanding cache usage and spotting pressure early.

Key monitoring commands

redis-cli INFO             # full stats snapshot
redis-cli INFO memory      # memory usage and limits
redis-cli INFO clients     # connected clients
redis-cli INFO stats       # ops/sec, hits, misses
redis-cli INFO keyspace    # keys per database

Live command stream

redis-cli MONITOR

Warning: MONITOR can reduce Redis throughput by up to 50% under high load. Use only for short debugging windows.

Key metrics to watch

  • used_memory_human vs maxmemory_human
  • keyspace_hits / keyspace_misses — cache hit ratio
  • connected_clients
  • evicted_keys — non-zero means memory pressure

ElastiCache CloudWatch metrics

Set alarms on BytesUsedForCache, CacheHits, CacheMisses, and Evictions. Alert when memory exceeds 80% of node capacity.

Copy S3 Objects Between AWS Accounts

Copying S3 objects between buckets in different AWS accounts requires cross-account permissions on both sides.

Step 1 – Destination bucket policy (Account B)

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::ACCOUNT_A_ID:root" },
    "Action": ["s3:PutObject", "s3:PutObjectAcl"],
    "Resource": "arn:aws:s3:::destination-bucket/*"
  }]
}

Step 2 – Sync from Account A

aws s3 sync s3://source-bucket s3://destination-bucket \
  --acl bucket-owner-full-control

--acl bucket-owner-full-control ensures Account B gains ownership of copied objects.

Step 3 – Verify

aws s3 ls s3://destination-bucket --recursive | head -20

For large-scale migrations (millions of objects), consider S3 Batch Operations or AWS DataSync.

Configure Postfix to Relay via Amazon SES

Configure Postfix to relay outbound mail through Amazon SES so server-generated emails use SES for reliable delivery.

Step 1 – Create SES SMTP credentials

In SES console → SMTP Settings → Create SMTP credentials. Note the SMTP username and password (not your AWS access keys).

Step 2 – Edit /etc/postfix/main.cf

relayhost = email-smtp.eu-west-1.amazonaws.com:587
smtp_sasl_auth_enable = yes
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_use_tls = yes
smtp_tls_security_level = encrypt

Step 3 – Create /etc/postfix/sasl_passwd

email-smtp.eu-west-1.amazonaws.com:587   SMTP_USERNAME:SMTP_PASSWORD
sudo chmod 600 /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd

Step 4 – Restart and test

sudo systemctl restart postfix
echo "Test" | mail -s "SES Test" recipient@example.com
sudo tail -f /var/log/mail.log

Sandbox mode: New SES accounts can only send to verified addresses. Request a sending limit increase to move to production.

Set Cache-Control Headers on S3 Objects with Lambda

S3 does not allow in-place metadata updates — you must copy the object back to itself with new metadata. This Lambda function triggers on PutObject events and sets the Cache-Control header.

Lambda function (Node.js)

const AWS = require('aws-sdk');
const s3 = new AWS.S3();

exports.handler = async (event) => {
    const bucket = event.Records[0].s3.bucket.name;
    const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
    const head = await s3.headObject({ Bucket: bucket, Key: key }).promise();
    await s3.copyObject({
        Bucket: bucket,
        CopySource: `${bucket}/${key}`,
        Key: key,
        MetadataDirective: 'REPLACE',
        ContentType: head.ContentType,
        CacheControl: 'max-age=31536000, public',
        Metadata: head.Metadata
    }).promise();
};

Setup

  1. Create a Lambda function with the code above (Node.js runtime).
  2. Attach an IAM role with s3:GetObject, s3:PutObject, s3:CopyObject on the bucket.
  3. Add an S3 trigger: Event type PUT, select your bucket.
  4. Test: aws s3api head-object --bucket my-bucket --key test.jpg | grep CacheControl

Back Up a Server to Amazon S3

Automate server backups to S3 using the AWS CLI and cron.

Step 1 – Install AWS CLI and configure credentials

sudo apt-get install -y awscli
aws configure

Step 2 – Create backup script at /usr/local/bin/backup-to-s3.sh

#!/bin/bash
DATE=$(date +%Y-%m-%d)
BUCKET="s3://my-server-backups"

tar -czf /tmp/www-$DATE.tar.gz /var/www/html
aws s3 cp /tmp/www-$DATE.tar.gz $BUCKET/www/
rm /tmp/www-$DATE.tar.gz

mysqldump -u root -pPASSWORD mydb | gzip > /tmp/db-$DATE.sql.gz
aws s3 cp /tmp/db-$DATE.sql.gz $BUCKET/db/
rm /tmp/db-$DATE.sql.gz
chmod +x /usr/local/bin/backup-to-s3.sh

Step 3 – Schedule daily at 2 AM

crontab -e
0 2 * * * /usr/local/bin/backup-to-s3.sh >> /var/log/s3-backup.log 2>&1

Step 4 – Set lifecycle rule to expire backups after 30 days

aws s3api put-bucket-lifecycle-configuration \
  --bucket my-server-backups \
  --lifecycle-configuration '{
    "Rules": [{"ID":"expire","Status":"Enabled","Prefix":"","Expiration":{"Days":30}}]
  }'

Install Sendy on AWS with Amazon SES

Sendy is a self-hosted email newsletter app that sends via Amazon SES at very low cost. Steps below cover Ubuntu/Debian with Apache.

Step 1 – Install dependencies

sudo apt-get update
sudo apt-get install -y apache2 php php-curl php-mysql curl

Step 2 – Enable Apache modules

sudo a2enmod rewrite expires headers
sudo systemctl restart apache2

Step 3 – Set upload permissions

chmod 777 /var/www/html/sendy/uploads/

Step 4 – Configure Sendy

Edit /var/www/html/sendy/includes/config.php with your database credentials and domain name.

Step 5 – Fix MySQL SQL mode (if needed)

Add to /etc/mysql/my.cnf under [mysqld]:

sql_mode = ""

Step 6 – Complete installation

Navigate to http://yourdomain.com/sendy to finish the web-based setup.

Auto-Remove Old AMIs and Snapshots (Bash Script)

This script deregisters AMIs tagged amitype=autoscale and deletes their snapshots if older than 7 days. Schedule via cron to automate AMI lifecycle management.

#!/bin/bash
REGION="us-east-1"
MAX_AGE_DAYS=7

aws ec2 describe-images \
  --filters "Name=tag:amitype,Values=autoscale" \
  --query 'Images[*].[ImageId,CreationDate]' \
  --output text --region $REGION | while read AMI_ID CREATED_AT; do

    CREATED_EPOCH=$(date -d "${CREATED_AT:0:10}" +%s)
    AGE_DAYS=$(( ($(date +%s) - CREATED_EPOCH) / 86400 ))

    if [ "$AGE_DAYS" -gt "$MAX_AGE_DAYS" ]; then
        echo "Deregistering $AMI_ID (age: ${AGE_DAYS}d)"
        SNAPS=$(aws ec2 describe-images --image-ids $AMI_ID \
          --query 'Images[*].BlockDeviceMappings[*].Ebs.SnapshotId' \
          --output text --region $REGION)
        aws ec2 deregister-image --image-id $AMI_ID --region $REGION
        for SNAP in $SNAPS; do
            aws ec2 delete-snapshot --snapshot-id $SNAP --region $REGION
        done
    fi
done

Warning: Test with echo before the deregister/delete commands in production. Deregistered AMIs and deleted snapshots cannot be recovered.

Schedule (daily at 3 AM)

0 3 * * * /usr/local/bin/cleanup-amis.sh >> /var/log/ami-cleanup.log 2>&1

Fix CloudFront CORS Issues (S3 + OAI)

CloudFront CORS errors occur when the Access-Control-Allow-Origin header is missing from cached responses. Fix it on S3, in CloudFront cache behaviour, and (if using Apache) in the web server config.

Step 1 – S3 CORS policy

S3 bucket → Permissions → CORS:

[{
  "AllowedHeaders": ["Origin", "Authorization"],
  "AllowedMethods": ["GET", "HEAD"],
  "AllowedOrigins": ["https://yourdomain.com", "https://www.yourdomain.com"],
  "MaxAgeSeconds": 3000
}]

Step 2 – Forward the Origin header in CloudFront

Distribution → Behaviors → edit: under Cache key and origin requests, whitelist the Origin header. This ensures CloudFront passes it to S3 and caches per-origin responses.

Step 3 – Set up Origin Access Identity (OAI)

  1. Distribution → Origins → edit S3 origin → Yes, use OAI.
  2. Update the S3 bucket policy to allow s3:GetObject from the OAI principal.

Step 4 – Invalidate the cache

aws cloudfront create-invalidation --distribution-id EXXXXXXX --paths "/*"

Resolve "Server Refused Our Key" SSH Errors on EC2

"Server refused our key" means the SSH server rejected the presented private key. Common causes and fixes:

1. Wrong file permissions

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

2. Wrong username

ssh -i key.pem ec2-user@IP    # Amazon Linux / RHEL
ssh -i key.pem ubuntu@IP      # Ubuntu
ssh -i key.pem admin@IP       # Debian

3. Public key missing from authorized_keys

ssh-keygen -f mykey.pem -y >> ~/.ssh/authorized_keys

4. SELinux context (RHEL / CentOS)

sudo restorecon -R -v ~/.ssh

Recovery when fully locked out

  • Use SSM Session Manager (if SSM agent is installed — no SSH needed).
  • Use the EC2 Serial Console (EC2 Instance Connect).
  • Follow the volume-detach procedure in the Change Key Pair guide above.

Verbose SSH debugging

ssh -vvv -i key.pem ec2-user@IP 2>&1 | grep -i "key\|auth\|refused"

Inject a New SSH Key via EC2 User Data

Inject a new SSH public key into a stopped instance via cloud-init User Data — no need to detach and mount volumes.

Steps

  1. Stop the instance.
  2. Actions → Instance Settings → Edit User Data.
  3. Enter (replace the key with your actual public key):
#cloud-config
ssh_deletekeys: false
ssh_authorized_keys:
  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD... your-key-here
  1. Save and start the instance. Cloud-init adds the key on boot.
  2. Verify SSH access with the new key.
  3. Clear the User Data afterward (set to empty, restart) to prevent re-injection.

Extract public key from a .pem file

ssh-keygen -f mykey.pem -y

Paste the output (ssh-rsa AAAA...) into the cloud-config above.

ssh_deletekeys: false preserves existing keys. Set to true to replace all keys and revoke old ones.

Redirect HTTP to HTTPS and WWW to Non-WWW (S3 + CloudFront + Route 53)

This setup canonicalises all requests to https://example.com using two S3 buckets, one CloudFront distribution, and Route 53.

Architecture

  • example.com S3 bucket → hosts actual content.
  • www.example.com S3 bucket → configured to redirect to example.com.
  • One CloudFront distribution in front of example.com with ACM SSL cert.
  • Route 53 Alias records for both pointing to CloudFront.

Step 1 – Main S3 bucket

Create example.com bucket, upload content, enable Static Website Hosting.

Step 2 – Redirect bucket

Create www.example.com bucket → Static Website Hosting → Redirect all requests → Target: example.com, Protocol: https.

Step 3 – ACM certificate (us-east-1)

Request a cert covering both example.com and www.example.com.

Step 4 – CloudFront distribution

Origin: example.com S3 website endpoint. Alternate domains: both subdomains. SSL: ACM cert. Viewer protocol: Redirect HTTP to HTTPS.

Step 5 – Route 53

Create Alias A records for example.com and www.example.com → CloudFront distribution.

AWS Credentials for the www-data User

Web server processes (running as www-data) need AWS credentials for services like S3 or SES. The recommended approach is an IAM Instance Role — never store static credentials in files.

Recommended: IAM Instance Role (no credential files needed)

  1. Create an IAM Role with the required policies (e.g., AmazonS3ReadOnlyAccess).
  2. Attach it to the EC2 instance: Actions → Security → Modify IAM role.
  3. The AWS SDK picks up credentials automatically from the instance metadata service (IMDS) — no configuration required.

Verify credential pickup as www-data

sudo -u www-data aws sts get-caller-identity

Alternative: credentials file (not recommended for production)

sudo mkdir -p /var/www/.aws
sudo tee /var/www/.aws/credentials <<EOF
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
region = us-east-1
EOF
sudo chown -R www-data:www-data /var/www/.aws
sudo chmod 600 /var/www/.aws/credentials

Mount Amazon EFS Automatically on Boot

Amazon EFS provides shared NFS storage accessible by multiple EC2 instances simultaneously. Use the EFS mount helper for automatic mounting with TLS.

Step 1 – Install amazon-efs-utils

# Ubuntu / Debian
sudo apt-get install -y nfs-common amazon-efs-utils

# Amazon Linux 2
sudo yum install -y amazon-efs-utils

Step 2 – Create the mount point

sudo mkdir -p /mnt/efs

Step 3 – Test mount manually

sudo mount -t efs -o tls fs-XXXXXXXX:/ /mnt/efs

Step 4 – Persist in /etc/fstab

fs-XXXXXXXX:/   /mnt/efs   efs   defaults,_netdev,tls   0   0

_netdev ensures the mount waits until the network is available at boot.

Step 5 – Apply and verify

sudo mount -a
df -h /mnt/efs

Security Group requirements

EC2 Security Group: allow outbound TCP 2049 to the EFS mount target.
EFS Security Group: allow inbound TCP 2049 from the EC2 Security Group.

Create an RDS MySQL User with Master-Equivalent Permissions

Amazon RDS for MySQL restricts the SUPER privilege from the master user. To create a second admin-equivalent user, grant the same privileges as the master user using SHOW GRANTS.

Step 1 – Check current master user grants

SHOW GRANTS FOR 'master_user'@'%';

The output lists all privileges. Typical RDS master grants include: SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, REFERENCES, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER.

Step 2 – Create the new user

CREATE USER 'new_admin'@'%' IDENTIFIED BY 'StrongPassword123!';

Step 3 – Grant master-equivalent privileges

GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS,
      REFERENCES, INDEX, ALTER, SHOW DATABASES,
      CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE,
      REPLICATION SLAVE, REPLICATION CLIENT,
      CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE,
      CREATE USER, EVENT, TRIGGER
ON *.* TO 'new_admin'@'%' WITH GRANT OPTION;

FLUSH PRIVILEGES;

Step 4 – Verify

SHOW GRANTS FOR 'new_admin'@'%';

Note

RDS does not allow granting SUPER, FILE, or SHUTDOWN privileges — these are reserved for AWS internal management. If your application requires SUPER, consider using RDS Proxy or restructuring the queries.

HTTP to HTTPS Redirect via Application Load Balancer

Application Load Balancers (ALB) can handle the HTTP → HTTPS redirect natively via a listener rule, eliminating the need to configure redirects in Apache or Nginx.

Step 1 – Ensure you have an HTTPS listener

Your ALB must have a listener on port 443 with an SSL certificate (from ACM or uploaded to IAM). If not, add one first.

Step 2 – Add a redirect rule to the HTTP:80 listener

  1. Go to EC2 → Load Balancers, select your ALB.
  2. Click the Listeners tab.
  3. Select the HTTP:80 listener → View/edit rules.
  4. Click the + (Add rule) button.
  5. Add a condition: IF Path is *
  6. Add an action: THEN Redirect to → HTTPS, Port 443, Status code 301.
  7. Save the rule.

Alternative: Replace the default action

You can also change the default action of the HTTP:80 listener directly to a redirect:

  1. Select HTTP:80 listener → Edit.
  2. Change default action to Redirect to HTTPS (301).
  3. Save.

Verify

curl -I http://yourdomain.com

You should see HTTP/1.1 301 Moved Permanently with a Location: https://... header.

Access EC2 Linux Instance over SSH without a .pem File

By default, EC2 Linux instances only accept key-based SSH authentication. To enable password-based SSH access (e.g., for a team member without the key), create a new user with a password and configure SSH to allow password auth.

Step 1 – SSH in with your .pem file

ssh -i your_pem_file.pem ubuntu@ec2-XXXXXX.compute-1.amazonaws.com

Step 2 – Create a new user

sudo useradd -s /bin/bash -m -d /home/USERNAME -g root USERNAME
sudo passwd USERNAME    # Set a strong password

Step 3 – Enable password authentication in SSH

sudo nano /etc/ssh/sshd_config

Find and change:

PasswordAuthentication yes

Restart SSH:

sudo systemctl restart sshd

Step 4 – Connect without .pem

ssh USERNAME@ec2-XXXXXX.compute-1.amazonaws.com

Security considerations

  • Password authentication is less secure than key-based auth. Only enable it in trusted environments.
  • Use fail2ban to block brute-force attempts if enabling password auth.
  • Consider using AWS Systems Manager Session Manager instead — it requires no SSH port open at all.
  • Ensure the EC2 Security Group does not expose port 22 to 0.0.0.0/0.

Install phpMyAdmin on Amazon Linux

Amazon Linux uses yum with EPEL, but phpMyAdmin may not be in the standard EPEL repo. Use the Remi repository to get a current version.

Step 1 – Enable EPEL and Remi repositories

sudo yum install -y epel-release
sudo rpm -Uvh http://rpms.remirepo.net/enterprise/remi-release-7.rpm

Step 2 – Install phpMyAdmin

sudo yum --enablerepo=remi install -y phpmyadmin

Step 3 – Configure phpMyAdmin to allow remote access

By default, phpMyAdmin only allows localhost. Edit /etc/phpMyAdmin/config.inc.php or the Apache alias config:

sudo nano /etc/httpd/conf.d/phpMyAdmin.conf

Change the Require local directive to allow your IP:

<RequireAny>
    Require ip YOUR_IP_ADDRESS
    Require local
</RequireAny>

Step 4 – Restart Apache and connect

sudo systemctl restart httpd

Access via http://YOUR_SERVER_IP/phpmyadmin.

Tip: Restrict access via Security Group

Additionally, restrict port 80 access to your IP in the EC2 Security Group to prevent public exposure of phpMyAdmin.

Configure phpMyAdmin to Connect Only to RDS

When your EC2 instance has phpMyAdmin installed but no local MySQL (using RDS instead), update the phpMyAdmin configuration to point to the RDS endpoint.

Edit the database config file

Edit /etc/phpmyadmin/config-db.php:

<?php
$dbuser = 'rds_master_username';
$dbpass = 'rds_master_password';
$basepath = '';
$dbname = 'mysql';
$dbserver = 'your-rds-endpoint.rds.amazonaws.com';
$dbport = '3306';
$dbtype = 'mysql';
?>

Alternatively, edit config.inc.php

$cfg['Servers'][$i]['host'] = 'your-rds-endpoint.rds.amazonaws.com';
$cfg['Servers'][$i]['port'] = '3306';
$cfg['Servers'][$i]['connect_type'] = 'tcp';

Ensure RDS Security Group allows access

The RDS instance's Security Group must allow inbound TCP 3306 from the EC2 instance's private IP or Security Group.

Test the connection

mysql -h your-rds-endpoint.rds.amazonaws.com -u rds_master_username -p

Import a VirtualBox VM into AWS (VM Import/Export)

Use the AWS VM Import/Export service to convert a VirtualBox VM into an EC2 AMI. The VM must be exported as OVA format and uploaded to S3 first.

Prerequisites

  • Export the VM from VirtualBox: File → Export Appliance → OVA format.
  • Upload the OVA to an S3 bucket: aws s3 cp myvm.ova s3://my-import-bucket/
  • AWS CLI configured with appropriate permissions.

Step 1 – Create the VM Import trust policy

Create trust-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "vmie.amazonaws.com" },
    "Action": "sts:AssumeRole",
    "Condition": {
      "StringEquals": { "sts:ExternalId": "vmimport" }
    }
  }]
}
aws iam create-role --role-name vmimport --assume-role-policy-document file://trust-policy.json

Step 2 – Attach permissions to the role

Create role-policy.json granting S3 read and EC2 snapshot write permissions, then attach:

aws iam put-role-policy --role-name vmimport   --policy-name vmimport   --policy-document file://role-policy.json

Step 3 – Start the import task

Create containers.json:

[{
  "Description": "My VirtualBox VM",
  "Format": "ova",
  "UserBucket": {
    "S3Bucket": "my-import-bucket",
    "S3Key": "myvm.ova"
  }
}]
aws ec2 import-image --description "My VM" --disk-containers file://containers.json

Step 4 – Monitor the import

aws ec2 describe-import-image-tasks --import-task-ids import-ami-XXXXXXXXXX

When status shows completed, the AMI is available in your EC2 console under AMIs.

Fix CloudFront CORS Errors for Web Fonts

Browsers enforce CORS for web fonts — a font loaded from a CDN (CloudFront) without the correct Access-Control-Allow-Origin header will be blocked. Fix this on both Apache/Nginx and S3.

Apache fix

Add to your VirtualHost or .htaccess:

<FilesMatch "\.(woff|woff2|ttf|otf|eot)$">
    Header set Access-Control-Allow-Origin "https://yourdomain.com"
</FilesMatch>

AddType application/font-woff2  .woff2
AddType application/font-woff   .woff

S3 CORS policy

In S3 → bucket → Permissions → CORS:

[{
  "AllowedHeaders": ["Origin"],
  "AllowedMethods": ["GET", "HEAD"],
  "AllowedOrigins": [
    "https://yourdomain.com",
    "https://www.yourdomain.com"
  ],
  "MaxAgeSeconds": 3000
}]

CloudFront — forward the Origin header

In the CloudFront distribution → Behaviors → edit the behavior:

  • Under Cache key and origin requests, add Origin to the forwarded headers.

This ensures CloudFront includes the CORS headers in cached responses per origin.

Invalidate the cache after making changes

aws cloudfront create-invalidation --distribution-id EXXXXXXX --paths "/fonts/*"

Run Moodle Behind an AWS Load Balancer with SSL Termination

When running Moodle behind an AWS Application Load Balancer (ALB) with SSL termination, Moodle receives traffic over HTTP internally but must generate https:// URLs for users. Without the correct configuration, Moodle detects HTTP and enters an infinite redirect loop.

Step 1 – Configure Moodle to trust the load balancer

Edit /var/www/html/moodle/config.php:

$CFG->wwwroot   = 'https://your-moodle-domain.com';
$CFG->sslproxy  = true;   // Tells Moodle it's behind an SSL proxy

Step 2 – Configure PHP to detect HTTPS from ALB

Add to the top of config.php (before the Moodle config block):

if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
    $_SERVER['HTTPS'] = 'on';
}

Step 3 – Apache or Nginx configuration

Apache — allow the ALB health check path without redirect:

RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} =http
RewriteCond %{REQUEST_URI} !^/moodle/login/index.php  # Exclude health check if needed
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

Step 4 – ALB configuration

  • HTTPS listener (443): forwards to EC2 on port 80.
  • HTTP listener (80): redirects to HTTPS (via ALB listener rule).
  • Health check path: use a Moodle path that returns 200 without requiring login (e.g., /moodle/login/index.php).
  • Stickiness: enable session stickiness on the target group (Moodle sessions are not shared by default).

Step 5 – Moodle session sharing (for multi-instance)

For multiple EC2 instances behind the ALB, configure Moodle to use a shared session store (ElastiCache Redis or memcached). Set in config.php:

$CFG->session_handler_class = '\\core\\session\\redis';
$CFG->session_redis_host = 'your-elasticache-endpoint';

Redirect Non-WWW to WWW and HTTP to HTTPS on ELB and Elastic Beanstalk

Elastic Beanstalk environments with an Application Load Balancer can handle domain canonicalisation (non-www → www) and HTTP → HTTPS redirects using ALB listener rules — no server-side configuration required.

Architecture

  • ALB HTTP:80 listener: redirect all traffic to HTTPS:443.
  • ALB HTTPS:443 listener: redirect non-www requests to www (or vice versa), then forward to the target group.

Step 1 – Add HTTP → HTTPS redirect (port 80 listener)

  1. In the EB console, go to Configuration → Load Balancer → Listeners.
  2. Edit the HTTP:80 listener's default action: set to Redirect to HTTPS:443, status 301.

Step 2 – Add www redirect rule (port 443 listener)

  1. Edit the HTTPS:443 listener rules.
  2. Add a rule: IF Host header is example.com → THEN Redirect to https://www.example.com#{path}?#{query}, status 301.
  3. The default rule (no condition) forwards to the target group normally.

Step 3 – Ensure both domains are in the SSL certificate

Your ACM certificate must cover both example.com and www.example.com (or use a wildcard *.example.com).

Step 4 – Update DNS

  • example.com → Alias to the EB/ALB endpoint
  • www.example.com → CNAME or Alias to the same EB/ALB endpoint

Useful AWS CLI Commands Reference

Common AWS CLI commands for identity checking, profile management, and troubleshooting authorization errors.

Identity and account

# Show current caller identity (User ID, Account, ARN)
aws sts get-caller-identity

# Show which profile is active
echo $AWS_DEFAULT_PROFILE
env | grep AWS_DEFAULT_PROFILE

# Set a specific profile for the current session
export AWS_DEFAULT_PROFILE=my-profile

# List all configured profiles
aws configure list-profiles

Decode authorization error messages

# When you receive an encoded authorization failure message (common with MFA/SCP)
aws sts decode-authorization-message --encoded-message <ENCODED_MSG>

List IAM users and roles

aws iam list-users --output table
aws iam list-roles --query 'Roles[*].[RoleName,Arn]' --output table

EC2 instance listing

# List running instances with Name tag and public IP
aws ec2 describe-instances   --filters "Name=instance-state-name,Values=running"   --query 'Reservations[*].Instances[*].[InstanceId,PublicIpAddress,Tags[?Key==\`Name\`].Value|[0]]'   --output table

MFA session token (for scripts requiring MFA)

aws sts get-session-token   --serial-number arn:aws:iam::ACCOUNT_ID:mfa/USERNAME   --token-code 123456

Export the resulting credentials as environment variables to use in subsequent CLI calls.

Reset the Administrator Password on an EC2 Windows Instance

If you lose the Windows Administrator password on an EC2 instance, AWS provides two methods to reset it: EC2Rescue via Systems Manager, or the Systems Manager Run Command.

Method 1 – Systems Manager Run Command (online, recommended)

  1. Ensure the instance has the SSM Agent running and an IAM role with the AmazonSSMManagedInstanceCore policy.
  2. Go to Systems Manager → Run Command.
  3. Select the AWSSupport-RunEC2RescueForWindowsTool document.
  4. Set Command to ResetAccess.
  5. Select the target instance and run.
  6. The new password is stored in Parameter Store. Retrieve it via:
    aws ssm get-parameter --name /ec2rescue/winpassword/instance-id --with-decryption

Method 2 – EC2Rescue offline (detach root volume)

  1. Stop the Windows instance.
  2. Detach the root EBS volume.
  3. Attach it to a rescue Windows EC2 instance as a secondary disk.
  4. Download and run EC2Rescue on the rescue instance, targeting the attached disk.
  5. Re-attach the disk to the original instance as the root volume and start it.

Retrieve password from .pem key (for a running instance with key pair)

  1. In the EC2 console, right-click the instance → Get Windows password.
  2. Upload your .pem private key file to decrypt the password.

This only works if the instance was launched with a key pair and hasn't had its password manually changed.

Nginx Configuration Behind an ALB for Magento 1

When Magento 1 runs behind an AWS ALB with SSL termination, Nginx must detect the original protocol from the X-Forwarded-Proto header to avoid redirect loops. The config below handles real IP forwarding and HTTPS detection.

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name example.com;
    root /var/www/html;
    index index.php index.html index.htm;

    # Trust ALB IP range for real client IP
    set_real_ip_from 0.0.0.0/0;
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;

        # Map the forwarded protocol so Magento/PHP can detect HTTPS
        if ($http_x_forwarded_proto = "https") {
            set $fastcgi_https "on";
        }

        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
        fastcgi_param HTTPS $fastcgi_https;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # Magento static files
    location ~* \.(jpg|jpeg|gif|png|css|js|ico|svg|woff|woff2|ttf)$ {
        expires 1y;
        add_header Cache-Control "public";
    }
}

Magento base URL settings

In Magento Admin → System → Configuration → Web, set the Secure Base URL to https://yourdomain.com/ and enable Use Secure URLs on Storefront.

Manage the Amazon SES Suppression List

Amazon SES maintains an account-level suppression list to prevent sending to addresses that previously bounced or complained. If a valid recipient ends up on this list, you need to remove them manually before SES will deliver to that address again.

List suppressed destinations

aws sesv2 list-suppressed-destinations   --region us-east-1   --output json | jq -r '.SuppressedDestinationSummaries[].EmailAddress'

Search for a specific address

aws sesv2 list-suppressed-destinations   --region us-east-1   --output json | jq -r '.SuppressedDestinationSummaries[].EmailAddress'   | grep -i "user@example.com"

Get suppression details for an address

aws sesv2 get-suppressed-destination   --email-address user@example.com   --region us-east-1

The output shows the reason (BOUNCE or COMPLAINT) and the date the address was suppressed.

Remove an address from the suppression list

aws sesv2 delete-suppressed-destination   --email-address user@example.com   --region us-east-1

Bulk removal

# Remove all suppressed addresses (use with caution!)
aws sesv2 list-suppressed-destinations   --region us-east-1   --output json | jq -r '.SuppressedDestinationSummaries[].EmailAddress' | while read email; do
    echo "Removing: $email"
    aws sesv2 delete-suppressed-destination --email-address "$email" --region us-east-1
done

Prevent future suppressions

In SES → Account-level suppression list, you can disable automatic suppression on bounce/complaint if you manage your own bounce handling. Only do this if you have robust bounce processing in place.