Install AcelleMail on AWS EC2

AWS EC2 is the right AcelleMail host when (a) you already operate on AWS and want to consolidate, (b) you want SES on the same network plane for sub-millisecond API latency, or (c) you need a specific region/VPC posture. The trade-off vs DigitalOcean: more flexibility, more dials, more bills to read. This guide is the AWS-specific overlay on the Ubuntu 24.04 install.

What this is for

AWS EC2 is the right host for AcelleMail when:

  • You already operate other workloads on AWS and want to consolidate.
  • You want SES on the same network plane as AcelleMail for sub-millisecond API call latency and free in-region egress (the biggest cost win).
  • Your compliance regime requires a specific AWS region or VPC posture.

The trade-off versus DigitalOcean: more flexibility, more dials, more bills to read.

This guide is the AWS-specific overlay on the Ubuntu 24.04 install. It covers the AWS service choices and the egress + port-25 quirks unique to AWS; the actual OS-level install commands match the bare-metal walkthrough.

👉 Canonical OS-level install: Install AcelleMail on Ubuntu 24.04 LTS — run after Steps 1–5 below.

Step 1 — Instance type

Tier Instance type $/mo on-demand $/mo 1-yr Reserved (no upfront)
Hobby t3.small (2 vCPU / 2 GB) + 30 GB gp3 ~$18 ~$11
Small (baseline) t3.medium (2 vCPU / 4 GB) + 50 GB gp3 ~$32 ~$20
Medium m6i.large (2 vCPU / 8 GB) + 100 GB gp3 ~$78 ~$50
Large m6i.xlarge (4 vCPU / 16 GB) + 200 GB gp3 ~$156 ~$98
XL c6i.2xlarge (8 vCPU / 16 GB) × 2 + RDS $300+ $190+

t3 is the burstable family — fine for AcelleMail at Hobby/Small tier where the workload is bursty (campaign blast, then idle). At Medium tier and up, switch to non-burstable m6i so a sustained worker push doesn't exhaust CPU credits.

Reserved Instances cut the bill by ~35-40% with no operational change. For any production workload running > 6 months, this is free money. The "no upfront" payment option lets you cancel mid-term with no penalty.

Spot Instances cut another ~70% but expose you to 2-minute eviction warnings. For AcelleMail, spot is reasonable for queue worker fleets (jobs are retryable; eviction loses at most one in-flight job) but not for the primary application instance — you don't want the admin UI to disappear mid-session.

The pre-built Ubuntu 24.04 LTS AMI (Canonical, owner 099720109477) is in every region. Find with:

aws ec2 describe-images --owners 099720109477 \
  --filters "Name=name,Values=ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*" \
  --query 'Images | sort_by(@, &CreationDate)[-1].ImageId' --output text

Step 2 — EBS volume

Use gp3 (general-purpose SSD) for the root volume. gp3 is faster and cheaper than gp2 (the older default), with explicit IOPS + throughput dials independent of size.

For AcelleMail at Small tier:

  • Size: 50 GB. Logs + the acellemail-latest.zip + DB grow surprisingly fast under heavy campaign volume.
  • IOPS: 3,000 (gp3 baseline; free).
  • Throughput: 125 MB/s (gp3 baseline; free).

For Medium tier and above with the database co-located, bump IOPS to 5,000-8,000 (paid). Or split the DB to RDS, which is cleaner.

Enable EBS snapshots as your droplet-level backup — schedule via Data Lifecycle Manager (free) for daily snapshots with 14-day retention.

Step 3 — Security Group

Create a Security Group with these rules:

Direction Port Source Purpose
Inbound 22 your-office-cidr SSH (lock to your IP — never 0.0.0.0/0 in production)
Inbound 80 0.0.0.0/0 HTTP (certbot challenge + redirect)
Inbound 443 0.0.0.0/0 HTTPS (admin UI + tracking endpoints)
Outbound All 0.0.0.0/0 Sending APIs + apt + composer

For an extra layer, attach AWS Systems Manager Session Manager instead of opening port 22 — SSH via the AWS console without any inbound rule (and with full audit trail).

Step 4 — Elastic IP

Allocate an Elastic IP (EIP) and associate it with the EC2 instance before pointing DNS at it. Same logic as DigitalOcean's Reserved IP — when you rebuild or migrate, the IP reassigns to the new instance with no DNS change and the sender reputation is preserved.

Cost note: an attached EIP is free; an unattached EIP is $3.65/month. Always associate before allocating, and release immediately when you tear down an instance.

Reverse DNS (PTR) for the EIP is set via the EC2 Email Sender Limit Removal form — same form that requests port-25 unblock. State the FQDN you want the PTR to resolve to (e.g. mail.example.com) and AWS sets it on your EIP within 24-48 hours. Without this, Gmail / Outlook will treat your sends as suspect — but if you're using SES (the recommended path), the SES IP pool has its own PTR setup and this becomes a non-issue.

Step 5 — RDS for MySQL (optional, recommended at Medium+)

RDS → Create Database
  Engine: MySQL 8.0
  Template: Production (Multi-AZ for HA) or Dev/Test (single AZ for cost)
  Instance: db.t3.micro (Hobby) → db.m6i.large (Medium+)
  Storage: 20 GB gp3, autoscaling enabled
  VPC: same VPC as EC2; subnet group spanning ≥ 2 AZs
  Public access: NO
  Security group: allow 3306 from EC2 SG only

Pick "Automated backups: 7 days" (free) and "Multi-AZ: yes" for production HA (doubles cost but adds failover replica).

The connection string from RDS plugs into the AcelleMail web installer in place of localhost/acellemail. Verify the RDS parameter group has character_set_server = utf8mb4 — some default parameter groups still ship utf8 (the 3-byte variant) which corrupts emoji subject lines and Vietnamese / Chinese characters.

Step 6 — Run the OS-level install

SSH in (or use Session Manager), then follow the canonical Ubuntu 24.04 install from Step 1 (System packages) through Step 10 (Web installer).

If you went RDS in Step 5 above, skip Step 3 (MySQL) of the Ubuntu guide and use the RDS endpoint instead.

For certbot on AWS, the standard --nginx plugin works on port 80, but if your Security Group blocks port 80 inbound (corporate policy), use the DNS challenge plugin against Route 53:

sudo apt install -y python3-certbot-dns-route53
sudo certbot certonly --dns-route53 -d mail.example.com \
  --non-interactive --agree-tos --email you@example.com

This requires the EC2 instance role to have route53:GetChange + route53:ChangeResourceRecordSets permissions on the relevant hosted zone — attach via IAM instance profile.

The web-installer wizard is the same regardless of host:

AcelleMail install wizard welcome page showing the 5-step top nav and the System Requirements card with green checkmarks

Full system requirements page — 14 PHP requirements all green plus 5 directory permission checks all green, with a Continue button at the bottom

Configuration step showing Site Name + License Key + Site Description fields plus an Admin Account card

Step 7 — SES integration (highly recommended)

AcelleMail's Amazon SES driver is the obvious sending choice when running on AWS — same network plane, no public-internet hop, free in-region egress. The setup:

  1. Verify your sending domain in SES in the same region as your EC2 (e.g. us-east-1). The SES verification flow generates SPF + DKIM DNS records to add at your DNS provider.
  2. Move out of SES sandbox — file a Support Center ticket with your sending pattern + use case. Amazon usually grants production access within 24 hours for legitimate use. Sandbox is 200/day, hard cap; production starts at 50k/day and ramps as your reputation builds.
  3. Create an IAM user with AmazonSESFullAccess (or scoped policy with just ses:SendRawEmail). Generate access key + secret.
  4. In AcelleMail Admin → Sending Servers → New → Amazon SES, paste credentials + region.
  5. Set sending quota matching your SES sandbox-out limit (start at 200/day, ramp per IP warmup schedule).
  6. See SES sending limits cookbook for quota management as you scale.

Port 25 on AWS

AWS blocks outbound port 25 by default on every EC2 instance. Unlike DigitalOcean, AWS rarely grants requests to unblock — the policy is to use SES instead. For 99% of AcelleMail users this is fine; SES via API speaks HTTPS on port 443 and never touches port 25.

If you have a hard requirement to send via outbound SMTP from EC2 (e.g., on-premises mail relay integration), file an AWS port-25 unblock request — it's an EC2 limit-increase request, not impossible but slow.

Egress cost — the AWS gotcha

EC2 egress to the public internet costs $0.09/GB in us-east-1 (cheaper in some regions, more expensive in others). AcelleMail itself is light on outbound traffic — the heavy egress is to the sending API.

When you use SES in the same region, traffic stays inside AWS's network and is free (private IP path). If you use Mailgun/SendGrid/etc. (external sending APIs), every email's body + headers go out as billable egress — a 50 KB email sent 100k times is 5 GB / month ≈ $0.45 in egress. Not big, but it compounds at scale.

Rule of thumb on AWS: use SES, not external sending APIs, unless you have a specific reason. The cost + latency advantage compounds with volume.

Step 8 — CloudWatch + alerts

CloudWatch Agent installs free; configure metrics + log shipping:

sudo apt install -y collectd
wget https://amazoncloudwatch-agent.s3.amazonaws.com/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
sudo dpkg -i amazon-cloudwatch-agent.deb
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard

CloudWatch Alarms — set the standard four:

Alarm Threshold Why
CPUUtilization > 80% for 5 min t3 credit exhaustion / sustained underprovisioning
Memory > 90% for 5 min (custom metric via agent) PHP-FPM + MySQL + Redis crossing safe limits
DiskSpaceUtilization > 85% Logs / queue spool runaway is the #1 silent failure
EstimatedCharges > $X/month (billing alarm) Single best AWS guardrail; catches a misconfigured campaign that suddenly racks up SES + egress

The billing alarm is the only one that catches "I accidentally re-imported the contact list 50 times" before it's a $5000 surprise.

Cost worked example — Small tier production

Line item $/month on-demand $/month 1-yr Reserved
EC2 t3.medium 32 20
50 GB gp3 EBS 4 4
Elastic IP (associated) 0 0
EBS snapshots (DLM) 2 2
RDS db.t3.micro Multi-AZ (optional) 30 18
CloudWatch logs + metrics (basic) 3 3
SES (50k – 500k sends, $0.10/1k) 5 – 50 5 – 50
Egress (SES is free) 1 1
Domain + Route 53 hosted zone 1.50 1.50
Total 48 – 124 35 – 100

Cheaper than DigitalOcean for the same workload only after you commit to a 1-year Reserved Instance. On-demand AWS is more expensive than DO; on a 1-year RI it's competitive. On a 3-year RI, AWS wins outright.

Common issues

What you see Likely cause Fix
t3.small CPU credit balance drops to 0 mid-campaign Workload exceeded t3 burst budget Upgrade to m6i.large (non-burstable) for sustained workloads
RDS connection fails from EC2 SG on RDS doesn't allow EC2 SG Edit RDS SG → inbound rule, source = EC2's SG (not its IP)
Vietnamese / emoji subject lines render as ??? after import RDS parameter group character_set_server is utf8, not utf8mb4 Create custom parameter group with utf8mb4, attach to RDS, reboot
Bill spikes unexpectedly after first campaign Egress to non-AWS sending API Migrate sending to SES in-region (free egress)
Certbot fails: "could not bind to port 80" SG blocks port 80 inbound Add port 80 rule, OR use --dns-route53 plugin (Step 6)
Sends slow / queued for hours t3 instance ran out of credits, OR worker not running Check CloudWatch CPU + supervisorctl status acellemail-worker:*
Session Manager won't connect SSM agent not installed (older AMIs) or instance role missing AmazonSSMManagedInstanceCore Attach the SSM policy to the instance profile; reboot
SES test send returns "Email address is not verified" Sandbox mode still active OR sending from unverified identity File support ticket to leave sandbox; verify the from-address in SES

FAQ

What about ECS / Fargate / EKS? Same container architecture as the Docker deployment guide. Use ECS Fargate with EFS for the AcelleMail code volume; EKS if you already operate Kubernetes. Both work; both are noticeably more complex than EC2 + EBS for a single-instance workload.

Should I use Lightsail instead? Lightsail is EC2 + RDS + Load Balancer + bundled egress at a flat monthly price. Easier billing, less flexibility. Reasonable for hobby and small production; outgrown at Medium tier when you start needing IAM-scoped policies and VPC controls. The install steps are identical to EC2 — same Ubuntu image, same Ondrej PPA, same vhost.

What region should I pick? Match your audience and your SES region. SES is region-aware; sending from us-east-1 to a us-east-1 SES endpoint is the lowest-latency, lowest-cost path. If your audience is EU, use eu-west-1 for both. Don't cross regions unless you have a specific reason.

Can I scale to multiple AZs? For a single-instance AcelleMail, you don't need to. Single AZ + EBS snapshots + IAM-controlled rebuild is sufficient HA for sending workloads (a 4-hour outage is annoying, not catastrophic). Multi-AZ kicks in at XL tier with multiple application instances behind an ALB — see Scaling for 100K+ Emails Per Day.

Reserved Instance vs Savings Plan? Both cut the same ~35-40%. Savings Plans are more flexible (apply to any instance family in any region) — recommended unless you're 100% sure of the instance shape. Pick "Compute Savings Plan" over "EC2 Instance Savings Plan" for maximum flexibility.

What about Graviton (ARM) instances? t4g / m6g are ~20% cheaper than the equivalent Intel SKUs. PHP 8.3 runs fine on ARM. Caveat: the official Ubuntu Ondrej PPA does ship ARM packages, but a small fraction of third-party PHP extensions (especially PECL ones) may need building from source. For a stock AcelleMail install with only the wizard-required extensions, Graviton works fine and saves money.

Related articles

15 comments

7 comments

  1. emma.whitaker
    Is the install wizard skipable for automaed deploys? Asking because we Terraform our infra and clicking through a wizard is awkward.
    1. admin
      right — for RDS specifically, you can change wait_timeout via the parameter group without a reboot if it's set as 'dynamic'. Most defaults are.
  2. linhvu.dev
    Any reason to use MariaDB over MySQL 8? We default to MariaDB everywhere but I see most Acelle install guides use MySQL.
    1. admin
      Short answer: yes — set the MySQL session variable from your worker's .env on boot and you'll get the longer timeout per connection. We'll add an explicit recipe in the next refresh.
    2. admin (edited)
      We tested this with up to 1M subscribers on a $40/mo VPS. Past that you start needing query optimization. Below that, the defaults are fine.
  3. phuong.mai.hn
    Followed this on Ubuntu 24.04 last week. Zero issues. The php-imap and php-sqlite3 notes saved me a wizard-error round-trip.
    1. admin (edited)
      Appreciate it. If anything in this needs updating, ping us — we revisit articles every few months...
  4. ravi.kumar.del…
    Clean walkthrough. The supervisor config copy-paste worked first try.
    1. admin (edited)
      Thanks. Pass it along if it helps your team
  5. linhpm.devs
    Installed on a $12/mo DigitalOcean droplet for our 30k-subscriber list. Performance has been fine. Memory peaks around 1.6 GB during batch sends; comfortable on 2GB.
    1. admin (edited)
      Thanks for the detail — adding the kernel-reboot edge case to the article on the next update.
    2. admin (edited)
      Great real-world detail. Your point about stale running_pid > 30 min as an alert is something we should add to the diagnostic flow
  6. sofia.costa.pt
    For anyone using systemd-resolved (Ubuntu 22+): set DNSStubListener=no in /etc/systemd/resolved.conf before installing. Otherwise port 53 conflicts when you eventually run a bounce handler.
  7. nadia.r.cl
    Is the install wizard skipable for automated deploys? Asking because we Terraform our infra and clicking through a wizard is awkward
    1. admin
      We don't recommend that approach in production. It works in dev but has subtle race conditions under concurrent load. Stick with the documented pattern...

More in Installation & Setup