Install AcelleMail on a DigitalOcean Droplet

DigitalOcean is one of the friendlier ways to run AcelleMail in production — cheap droplets, simple console, and a managed Cloud Firewall that keeps SMTP exposure honest. This guide walks the DigitalOcean-specific bits end-to-end: droplet sizing, image choice, Reserved IP, Cloud Firewall, and the PTR / reverse-DNS gotcha that bites every new sender.

What this is for

DigitalOcean is one of the friendliest hosts for AcelleMail in production — cheap droplets, simple console, easy snapshots, and a managed Cloud Firewall that keeps SMTP exposure honest. This guide covers the DigitalOcean-specific bits end-to-end:

  • Droplet sizing
  • Image choice
  • Reserved IP for stable SMTP identity
  • Cloud Firewall rules for SMTP ports
  • The PTR / reverse-DNS gotcha that bites every new sender
  • Networking quirks (private vs public IPv4)

Once the droplet is provisioned, the OS-level install (PHP, MySQL, nginx, certbot, supervisor, the AcelleMail web installer) is identical to a bare-metal Ubuntu 24.04 install — so we defer those steps to the canonical guide rather than duplicate them here.

👉 Canonical OS-level install: Install AcelleMail on Ubuntu 24.04 LTS — follow it from Step 1 once the droplet is up.

Step 1 — Pick a droplet size

AcelleMail's resource needs scale with the queue (concurrent sends), the contact-list size (CSV import memory), and the campaign throughput. Match the Server Requirements tiers to DigitalOcean's catalog:

AcelleMail tier Sends / month Droplet shape (Premium AMD) Monthly Notes
Hobby < 50k 1 vCPU, 2 GB, 60 GB SSD ~$14 Workable for testing; tight on real campaigns
Small (baseline) 50k – 500k 2 vCPU, 4 GB, 80 GB SSD ~$28 The recommended starting point
Medium 500k – 2M 4 vCPU, 8 GB, 160 GB SSD ~$56 Bump queue workers to 4
Large > 2M 8 vCPU, 16 GB, 320 GB SSD ~$112 Split MySQL to a managed DB instance

The Premium AMD line (not Regular) gives newer EPYC silicon, NVMe-backed storage, and noticeably faster MySQL writes — for a workload that's queue-heavy plus database-heavy, the ~$4/month delta over Regular pays for itself in throughput.

Why not the $4 Basic droplet? It's 1 vCPU + 512 MB RAM. PHP-FPM + MySQL + Redis + Supervisor + nginx don't fit in 512 MB once a real campaign runs — you'll thrash swap and time out. Skip it for production.

Step 2 — Pick the image

In the DigitalOcean Create Droplet flow:

  • Image: Ubuntu → 24.04 (LTS) x64
  • Region: the one closest to your subscribers (latency matters less than reputation, but pick the right country for GDPR / data-residency reasons)
  • VPC / Networking: default VPC is fine; keep public IPv4 enabled (you'll need it to receive SMTP)
  • SSH Keys: add your laptop key — do not enable password authentication
  • Hostname: set this to the exact mail subdomain you'll send from, e.g. mail.example.com. This is critical — see Step 5 (Reverse DNS).

If you prefer the CLI:

doctl compute droplet create mail.example.com \
  --image ubuntu-24-04-x64 \
  --size s-2vcpu-4gb-amd \
  --region sgp1 \
  --ssh-keys "$(doctl compute ssh-key list --format ID --no-header | tr '\n' ',' | sed 's/,$//')" \
  --enable-monitoring \
  --wait

The --enable-monitoring flag installs the DigitalOcean monitoring agent — useful for the Disk / Memory alerts you'll set up in Step 7.

Step 3 — Lock down SSH access (do this before anything else)

The droplet boots with the root account exposed to the world on port 22. First SSH in and create a non-root sudo user:

ssh root@<droplet-ip>
adduser acelle                        # set a strong password
usermod -aG sudo acelle
rsync --archive --chown=acelle:acelle ~/.ssh /home/acelle/
# from your laptop, verify:
ssh acelle@<droplet-ip>

Then disable root + password SSH:

sudo sed -i 's/^#\?PermitRootLogin .*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl reload ssh

Test from a second terminal before closing the root session — if the new user can't SSH, you'll need the original session to roll back.

Step 4 — Cloud Firewall

DigitalOcean's Cloud Firewall is a free, stateful, edge-level filter. Use it instead of (or in addition to) UFW — Cloud Firewall blocks traffic before it reaches the droplet, so it survives any kernel-level mistake.

Create a firewall via Console → Networking → Firewalls → Create Firewall, with these inbound rules:

Type Protocol Port Source Purpose
SSH TCP 22 Your laptop IP / VPN CIDR Admin access — never 0.0.0.0/0 long-term
HTTP TCP 80 All IPv4, All IPv6 Web admin + certbot HTTP-01 challenge
HTTPS TCP 443 All IPv4, All IPv6 Web admin (TLS) + AcelleMail tracking endpoints
Custom TCP 587 All IPv4, All IPv6 Inbound SMTP submission (only if customers send via SMTP API)
Custom TCP 465 All IPv4, All IPv6 Inbound SMTPS submission (same, TLS-wrapped)

Outbound rules — leave at "All TCP, All UDP, All ICMP to All IPv4/IPv6". AcelleMail makes outbound connections to SES/SendGrid/Mailgun/SparkPost/Gmail Postmaster + DNS + package mirrors; restricting these is more pain than payoff for a sender.

Note on port 25: DigitalOcean blocks outbound port 25 on every new account by default. If you plan to send via your own server's SMTP (instead of SES / SendGrid / Mailgun / SparkPost relays), you must open a support ticket asking them to enable outbound port 25 — they'll usually approve after asking for your sending plan and verifying your account is in good standing. For most installs, route through a relay (Configuring Amazon SES with AcelleMail) — it's a better deliverability story anyway.

Step 5 — Reverse DNS (PTR) — the gotcha

This is the #1 deliverability mistake on DigitalOcean for AcelleMail users. Receivers (Gmail, Outlook, Yahoo) check that your sender IP's PTR record resolves back to the same hostname you HELO with. If it doesn't, expect bulk-folder placement at best, outright rejection at worst.

How DigitalOcean sets PTR: the droplet's PTR record is automatically generated from the droplet's name (not its hostname inside the OS). So if you named your droplet mail.example.com in Step 2, the PTR is already correct:

dig +short -x <droplet-ip>
# Expect: mail.example.com.

If you got the droplet name wrong (e.g. you named it acellemail-prod-1), fix it via Console → Droplet → ... → Rename:

  1. Set the droplet's name field to mail.example.com (the FQDN, including the subdomain)
  2. Wait 5 minutes
  3. Re-run dig +short -x <droplet-ip> until it returns mail.example.com.
  4. Also set the in-OS hostname so they match: sudo hostnamectl set-hostname mail.example.com

You must also have a forward A record (mail.example.com → <droplet-ip>) at your DNS provider — the PTR ↔ A pair must match in both directions, or receivers will treat the IP as untrusted.

For why this matters, see the deliverability primer: Reverse DNS and PTR Records (in the DNS Setup guide).

Step 6 — Reserved IP (recommended)

A Reserved IP (DigitalOcean's name for a Floating IP / Elastic IP) is a static public IPv4 that you can detach from one droplet and reattach to another. For AcelleMail, the value is:

  • Reputation continuity during disaster recovery — if your droplet fails and you restore from a snapshot, the new droplet inherits the same sending IP, so the warmed reputation is preserved.
  • Easier upgrades — same trick during a OS-version migration (provision new droplet, snapshot-restore data, detach Reserved IP from old, attach to new).

Cost: free while attached to a running droplet, $4/month if held against a stopped droplet (or unassigned). Activate via Console → Networking → Reserved IPs → Assign.

Update your PTR to point at the Reserved IP once attached — Reserved IPs have their own PTR record, separate from the droplet's primary public IP. Use Console → Networking → Reserved IPs → click the IP → Edit reverse DNS → set mail.example.com.

If you skip the Reserved IP, that's fine for a first install — you can always assign one later. Just know that snapshot-restore will give you a new IP, and you'll have to redo PTR + warmup.

Step 7 — Monitoring + alerts (free with DigitalOcean Monitoring)

Console → Monitoring → Create Alert Policy. The four alerts every AcelleMail droplet should have:

Metric Threshold Why
Memory utilisation > 85% for 10 min PHP-FPM + MySQL + Redis combined — crossing 85% means you're swapping
Disk utilisation > 80% Logs + queue spool + database — silent runaway is the #1 cause of "sends just stopped" tickets
CPU utilisation > 90% for 30 min Sustained means underprovisioned; bump to the next tier
Outbound traffic > 5 GB / hour Detects spam-incident runaway (compromised account, misconfigured campaign)

These cost nothing and the email notifications go to your DO account email by default.

Step 8 — Now follow the canonical Ubuntu 24.04 install

The droplet is provisioned, networked, firewalled, and your sudo user is set up. Everything from here is the same as bare-metal:

👉 Continue at: Install AcelleMail on Ubuntu 24.04 LTS, Step 1

You'll do:

  • Step 1 — System packages (apt update, apt upgrade, basics)
  • Step 2 — PHP 8.3 + extensions (Ondrej PPA; must include php8.3-imap and php8.3-sqlite3, both wizard-blocking)
  • Step 3 — MySQL 8.0 + database + user (utf8mb4 charset — non-negotiable)
  • Step 4 — Redis 7 (recommended for the queue + cache)
  • Step 5 — Nginx 1.24 + vhost (use mail.example.com as server_name)
  • Step 6 — Drop in the AcelleMail bundle (acellemail-latest.zip to /var/www/acellemail/)
  • Step 7 — TLS with certbot (--nginx -d mail.example.com --redirect)
  • Step 8 — Supervisor for the queue worker (start with numprocs=2)
  • Step 9 — Cron (* * * * * php artisan schedule:run)
  • Step 10 — Web installer wizard

The web-installer wizard screens look like this on DigitalOcean (the wizard is identical regardless of host):

AcelleMail install wizard welcome page showing the 5-step top nav (System Check → Configuration → Database → Background Jobs → Finish) 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

Post-droplet verification (specific to DigitalOcean)

After completing the canonical install, run these checks for DigitalOcean-specific health:

# 1. PTR matches HELO hostname
dig +short -x $(curl -s https://api.ipify.org)
# Expect: mail.example.com.

# 2. Outbound port 25 (only if you opened a ticket to enable it)
nc -zvw 5 smtp.gmail.com 25
# Expect: succeeded — if "timed out", DO is still blocking port 25 for your account

# 3. DigitalOcean Monitoring agent is running
systemctl status do-agent
# Expect: active (running)

# 4. Reserved IP is attached (if you assigned one)
ip -4 addr show eth0 | grep inet
# Expect: BOTH the primary droplet IP AND the Reserved IP listed

If port 25 is blocked and you need outbound SMTP for direct sending, open a DigitalOcean support ticket: "Please remove outbound port 25 restriction for droplet <id>. We're running AcelleMail (a transactional email platform) and need direct SMTP for FBL processing." Approval is usually within 24 hours for accounts in good standing.

Common issues

What you see Likely cause Fix
dig +short -x <ip> returns nothing Droplet wasn't named with the FQDN Console → Droplet → ... → Rename to mail.example.com, wait 5 min
dig +short -x <reserved-ip> returns the droplet's name (not mail.) Reserved IP has its own separate PTR Console → Networking → Reserved IPs → Edit reverse DNS
Cloud Firewall created but droplet still wide-open Firewall not yet assigned to the droplet Edit firewall → Droplets tab → add the droplet
Outbound mail.log shows port 25 timeout to every relay DO outbound port 25 block (default on new accounts) Open support ticket; meanwhile use SES/SendGrid via 587 or 465
Web installer wizard shows red on IMAP or SQLite3 Missing PHP extension sudo apt-get install -y php8.3-imap php8.3-sqlite3 && sudo systemctl restart php8.3-fpm
Random "Connection refused" from worker to MySQL MySQL bound to 127.0.0.1 only but worker user is in a different namespace Verify bind-address in /etc/mysql/mysql.conf.d/mysqld.cnf is 127.0.0.1 and connection string in .env uses 127.0.0.1 (not localhost, which prefers Unix socket)
Sends suddenly stop after working for weeks Disk full (probably /var/log or storage/logs/laravel.log) df -h; sudo find /var/www/acellemail/storage/logs -name "*.log" -mtime +14 -delete
certbot --nginx -d mail.example.com fails with "Connection refused" nginx not running or port 80 blocked at Cloud Firewall Verify nginx is up; verify Cloud Firewall has port 80 inbound from 0.0.0.0/0

FAQ

Can I use DigitalOcean's App Platform instead of a Droplet? No — AcelleMail requires a real Linux server (long-running queue workers via supervisor, system cron, writable filesystem). App Platform is for stateless web apps; AcelleMail isn't one.

Should I use a Managed Database for MySQL? For Small / Medium tiers, no — bundled MySQL on the droplet is faster (no network round-trip) and cheaper. For Large tier (> 2M sends/month) or multi-droplet setups, yes — pick the smallest plan, set utf8mb4 at the cluster level, and point AcelleMail's .env at the private connection string. See Scaling for 100K+ Emails Per Day.

What about DigitalOcean Spaces for asset storage? Not needed — AcelleMail stores tracking pixels and assets locally under storage/. Don't try to offload them to Spaces; the click/open tracking writes are too hot for object storage round-trips.

My droplet got a different IP after a power cycle — why? Droplets keep their public IPv4 across reboots. They only get a new one if you (a) recreate from snapshot into a new droplet, or (b) detach + reattach to a different region. Use a Reserved IP if you can't tolerate any IP churn (Step 6).

Is DigitalOcean's Cloud Firewall enough — do I still need UFW? Cloud Firewall alone is enough for most installs (it filters at the hypervisor, before traffic reaches the OS). UFW adds a second layer of defence and is useful when you're running multiple services with overlapping ports. For an AcelleMail-only droplet, Cloud Firewall alone is fine.

Can I run AcelleMail on a DigitalOcean GPU droplet? Yes but pointless — AcelleMail is CPU + I/O bound, not GPU-accelerated. Use a standard Premium AMD droplet.

Related articles

17 commenti

7 commenti

  1. jmorrison.itop…
    Is the install wizard skipable for automated deploys? Asking because we Terraform our infra and clicking through a wizard is awkward.
    1. admin
      Good question — and one that comes up often enough we should add an FAQ section. Short answer: yes for the common case; the exception is when you're running custom plugins that override the default behavior...
  2. anna.k.pm
    any reason to use mariadb over mysql 8? we default to mariadb everywhere but i see most acelle install guides use mysql.
    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.
  3. d.cohen.tlv
    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 (modificato)
      Thanks for the kind words. We try to keep these source-grounded so they age well.
  4. cw.dev.sh
    Clean walkthrough. The supervisor config copy-paste worked first try.
    1. admin (modificato)
      Thanks. Pass it along if it helps your team.
    2. admin (modificato)
      glad it landed. drop suggestions in the comments and we'll incorporate them on the next refresh.
    3. admin (modificato)
      appreciate it. if anything in this needs updating, ping us — we revisit articles every few months.
  5. cmendoza.mx
    we use hetzner instead of do — same ubuntu image, identical install. probably $4/mo cheaper. the ptr record on hetzner requires opening a support ticket but they respond same-day.
    1. admin
      Thanks for sharing. The pattern you describe is exactly the use case we built that feature for — glad it landed for you.
    2. admin (modificato)
      Thanks for the detail — adding the kernel-reboot edge case to the article on the next update.
  6. femi.adeyemi
    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...
    1. admin
      Good tip. The Cloudflare-outbound-rate-limit case is something we hadn't documented.
  7. nadia.r.cl
    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 (modificato)
      Great real-world detail. Your point about stale running_pid > 30 min as an alert is something we should add to the diagnostic flow.

More in Installation & Setup