What You Are Building

A phishing simulation platform for authorised security awareness training. The stack is GoPhish for campaign management, Postfix for mail delivery, Apache for landing pages, and Let's Encrypt for TLS — all on a single Ubuntu 24.04 LTS host. You will configure every DNS record a sending domain needs to deliver reliably and look legitimate to mail filters: SPF, DKIM, DMARC, CAA and the service-discovery SRV records that modern mail infrastructure expects.

This is an offensive-security tool. Use it only for authorised testing within your own organisation or under a signed engagement letter.

Prerequisites

Step 1: System Preparation

sudo apt update && sudo apt upgrade -y
sudo apt install -y unzip curl wget certbot ufw
sudo timedatectl set-timezone UTC

Set the hostname to your primary sending domain:

sudo hostnamectl set-hostname mail.phishdomain.com

Edit /etc/hosts:

127.0.0.1   localhost
203.0.113.10  mail.phishdomain.com mail

Firewall

sudo ufw allow 22/tcp
sudo ufw allow 25/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 3333/tcp
sudo ufw enable

Port 3333 is the GoPhish admin panel. In production, restrict it to your management IP:

sudo ufw delete allow 3333/tcp
sudo ufw allow from 198.51.100.0/24 to any port 3333

Step 2: Install Postfix

sudo apt install -y postfix

Select Internet Site when prompted. Set the system mail name to your primary sending domain (e.g. phishdomain.com).

Configure Postfix

Edit /etc/postfix/main.cf:

# ─── identity ─────────────────────────────────────────────
myhostname = mail.phishdomain.com
mydomain = phishdomain.com
myorigin = $mydomain
mydestination = $myhostname, localhost.$mydomain, localhost

# ─── network ──────────────────────────────────────────────
inet_interfaces = all
inet_protocols = ipv4
mynetworks = 127.0.0.0/8 [::1]/128

# ─── TLS ──────────────────────────────────────────────────
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.phishdomain.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.phishdomain.com/privkey.pem
smtpd_tls_security_level = may
smtpd_tls_protocols = >=TLSv1.2
smtp_tls_security_level = may
smtp_tls_protocols = >=TLSv1.2
smtp_tls_loglevel = 1

# ─── delivery ─────────────────────────────────────────────
virtual_alias_maps = hash:/etc/postfix/virtual
transport_maps = hash:/etc/postfix/transport

# ─── hardening ────────────────────────────────────────────
smtpd_banner = $myhostname ESMTP
disable_vrfy_command = yes
smtpd_helo_required = yes
strict_rfc821_envelopes = yes
message_size_limit = 10240000

# ─── rate limiting ────────────────────────────────────────
smtp_destination_concurrency_limit = 2
smtp_destination_rate_delay = 3s
smtp_extra_recipient_limit = 5
default_destination_rate_delay = 1s

The rate-limiting settings are critical. Phishing simulation servers that blast mail at full speed get blacklisted within hours. Two concurrent connections per destination with a three-second delay between messages keeps you under the radar of receiving mail servers.

Virtual Aliases

If you want all inbound mail on your phishing domains forwarded to a single collection address, create /etc/postfix/virtual:

@phishdomain.com    phishing-testing@your-real-domain.com
@seconddomain.com   phishing-testing@your-real-domain.com

Build the hash and reload:

sudo postmap /etc/postfix/virtual
sudo systemctl reload postfix

Transport Maps

If different domains should route through different relays, create /etc/postfix/transport:

phishdomain.com    smtp:
seconddomain.com   smtp:
sudo postmap /etc/postfix/transport

Step 3: TLS Certificates

Obtain certificates for each sending domain and the mail hostname. You need these before GoPhish landing pages will work over HTTPS.

sudo certbot certonly --standalone -d mail.phishdomain.com
sudo certbot certonly --standalone -d phishdomain.com
sudo certbot certonly --standalone -d seconddomain.com

Enable automatic renewal:

sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer

Step 4: Install GoPhish

Download the latest release:

cd /opt
sudo wget https://github.com/gophish/gophish/releases/download/v0.12.1/gophish-v0.12.1-linux-64bit.zip
sudo unzip gophish-v0.12.1-linux-64bit.zip -d gophish
sudo chmod +x /opt/gophish/gophish

Check the GoPhish releases page for the current version.

Configure GoPhish

Edit /opt/gophish/config.json:

{
  "admin_server": {
    "listen_url": "0.0.0.0:3333",
    "use_tls": true,
    "cert_path": "/etc/letsencrypt/live/mail.phishdomain.com/fullchain.pem",
    "key_path": "/etc/letsencrypt/live/mail.phishdomain.com/privkey.pem"
  },
  "phish_server": {
    "listen_url": "0.0.0.0:8080",
    "use_tls": false
  },
  "db_name": "sqlite3",
  "db_path": "gophish.db",
  "migrations_prefix": "db/db_",
  "contact_address": "",
  "logging": {
    "filename": "/opt/gophish/gophish.log",
    "level": "info"
  }
}

The phish server listens on 8080 without TLS because Apache will reverse-proxy it on port 443. Running GoPhish directly on 443 requires root; the reverse-proxy approach is cleaner.

Create a Systemd Service

Create /etc/systemd/system/gophish.service:

[Unit]
Description=GoPhish Phishing Simulation
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/gophish
ExecStart=/opt/gophish/gophish
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
User=root

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable gophish
sudo systemctl start gophish

First Login

Open https://your-server-ip:3333 in your browser. The default credentials are printed to the GoPhish log on first start:

sudo journalctl -u gophish --no-pager -n 20 | grep "Please login"

Log in and change the password immediately.

Step 5: Apache Reverse Proxy for Landing Pages

GoPhish serves landing pages on port 8080. Apache proxies these on port 443 with proper TLS termination, and you can serve different landing pages for different domains using virtual hosts.

sudo apt install -y apache2
sudo a2enmod proxy proxy_http ssl rewrite headers

Create a virtual host for each phishing domain. Example for /etc/apache2/sites-available/phishdomain.conf:

<VirtualHost *:80>
    ServerName phishdomain.com
    RewriteEngine On
    RewriteRule ^(.*)$ https://%{SERVER_NAME}$1 [R=301,L]
</VirtualHost>

<VirtualHost *:443>
    ServerName phishdomain.com

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/phishdomain.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/phishdomain.com/privkey.pem

    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/

    RequestHeader set X-Forwarded-Proto "https"

    <IfModule mod_headers.c>
        Header always set Strict-Transport-Security "max-age=31536000"
        Header always set X-Content-Type-Options "nosniff"
    </IfModule>

    ErrorLog ${APACHE_LOG_DIR}/phish_error.log
    CustomLog ${APACHE_LOG_DIR}/phish_access.log combined
</VirtualHost>

Enable the site:

sudo a2ensite phishdomain.conf
sudo a2dissite 000-default.conf
sudo systemctl reload apache2

Repeat for each sending domain, changing ServerName and certificate paths.

Step 6: DNS Configuration

This is where campaigns succeed or fail. Every sending domain needs a complete set of DNS records. Missing any one of them triggers spam filters at major providers.

A and MX Records

phishdomain.com.      A       203.0.113.10
mail.phishdomain.com. A       203.0.113.10
phishdomain.com.      MX  10  mail.phishdomain.com.

PTR Record (Reverse DNS)

Contact your hosting provider to set the PTR record for your server's IP. It must resolve to mail.phishdomain.com (your myhostname). Many providers expose this in their control panel.

10.113.0.203.in-addr.arpa.  PTR  mail.phishdomain.com.

Without a matching PTR, Gmail and Microsoft 365 will reject or spam-folder your mail. This is the single most common reason phishing simulations fail to deliver.

SPF Record

SPF tells receiving servers which IPs are authorised to send on behalf of your domain:

phishdomain.com.  TXT  "v=spf1 a mx ip4:203.0.113.10 -all"

Use -all (hard fail), not ~all (soft fail). A soft fail tells receivers that unauthorised senders are suspicious but acceptable. A hard fail says they are definitively rejected. This improves your deliverability because it signals a well-configured domain.

DKIM

Install OpenDKIM:

sudo apt install -y opendkim opendkim-tools

Generate a key for each sending domain:

sudo mkdir -p /etc/opendkim/keys/phishdomain.com
sudo opendkim-genkey -b 2048 -d phishdomain.com -D /etc/opendkim/keys/phishdomain.com -s mail -v
sudo chown -R opendkim:opendkim /etc/opendkim

Configure /etc/opendkim.conf:

Syslog          yes
Canonicalization relaxed/simple
Mode            sv
SubDomains      no
AutoRestart     yes
SignatureAlgorithm rsa-sha256

KeyTable        refile:/etc/opendkim/key.table
SigningTable    refile:/etc/opendkim/signing.table
InternalHosts   refile:/etc/opendkim/trusted.hosts
ExternalIgnoreList refile:/etc/opendkim/trusted.hosts

Create /etc/opendkim/signing.table:

*@phishdomain.com    mail._domainkey.phishdomain.com
*@seconddomain.com   mail._domainkey.seconddomain.com

Create /etc/opendkim/key.table:

mail._domainkey.phishdomain.com    phishdomain.com:mail:/etc/opendkim/keys/phishdomain.com/mail.private
mail._domainkey.seconddomain.com   seconddomain.com:mail:/etc/opendkim/keys/seconddomain.com/mail.private

Create /etc/opendkim/trusted.hosts:

127.0.0.1
localhost
mail.phishdomain.com

Set the socket in /etc/default/opendkim:

SOCKET="inet:8891@localhost"

Add to /etc/postfix/main.cf:

milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:localhost:8891
non_smtpd_milters = $smtpd_milters

Publish the DKIM public key. Display it:

sudo cat /etc/opendkim/keys/phishdomain.com/mail.txt

Add the output as a TXT record for mail._domainkey.phishdomain.com. Repeat for every sending domain.

Restart both:

sudo systemctl restart opendkim
sudo systemctl restart postfix

DMARC Record

_dmarc.phishdomain.com.  TXT  "v=DMARC1; p=quarantine; rua=mailto:dmarc@phishdomain.com; adkim=r; aspf=r; pct=100"

CAA Records

CAA (Certificate Authority Authorization) records specify which certificate authorities are permitted to issue TLS certificates for your domain. Without a CAA record, any CA can issue a certificate; with one, only the listed CAs can.

For phishing simulation domains using Let's Encrypt:

phishdomain.com.  CAA  0 issue "letsencrypt.org"
phishdomain.com.  CAA  0 issuewild ";"
phishdomain.com.  CAA  0 iodef "mailto:security@your-real-domain.com"

This achieves two things. First, it prevents an attacker from obtaining a certificate for your simulation domain from a different CA, which would let them impersonate your phishing infrastructure. Second, it signals to security tools that the domain is properly managed — a bare domain with no CAA record is a minor flag in some email reputation systems.

If you use multiple CAs, add a separate issue record for each:

phishdomain.com.  CAA  0 issue "letsencrypt.org"
phishdomain.com.  CAA  0 issue "sectigo.com"

SRV Records

SRV (Service) records advertise the location and port of specific services. For a mail domain, these records tell clients and other servers where to find your mail services. They are not strictly required for delivery, but their presence makes a domain look like a properly configured mail environment — which is exactly what you want for simulation domains that need to pass scrutiny.

Mail Submission (RFC 6186)

These records allow mail clients to auto-discover your server settings:

_submission._tcp.phishdomain.com.  SRV  0 1 587 mail.phishdomain.com.
_imaps._tcp.phishdomain.com.       SRV  0 1 993 mail.phishdomain.com.
_imap._tcp.phishdomain.com.        SRV  0 0 0   .
_pop3._tcp.phishdomain.com.        SRV  0 0 0   .
_pop3s._tcp.phishdomain.com.       SRV  0 0 0   .

MTA-STS

MTA-STS (Mail Transfer Agent Strict Transport Security) tells sending servers to require TLS when delivering to your domain. It works via a DNS record and a policy file:

_mta-sts.phishdomain.com.  TXT  "v=STSv1; id=20260910"

The id is an opaque string that changes when you update the policy. Receiving servers cache the policy until the id changes.

Serve the policy file at https://mta-sts.phishdomain.com/.well-known/mta-sts.txt:

version: STSv1
mode: enforce
mx: mail.phishdomain.com
max_age: 86400

Add an Apache virtual host for this subdomain, or serve it from your existing configuration.

SMTP TLS Reporting (TLSRPT)

Tells sending servers where to report TLS negotiation failures:

_smtp._tls.phishdomain.com.  TXT  "v=TLSRPTv1; rua=mailto:tls-reports@your-real-domain.com"

Complete DNS Zone Example

For reference, here is every record a fully configured sending domain needs:

; ─── A / MX ───────────────────────────────────────────────
phishdomain.com.          A       203.0.113.10
mail.phishdomain.com.     A       203.0.113.10
mta-sts.phishdomain.com.  A       203.0.113.10
phishdomain.com.          MX  10  mail.phishdomain.com.

; ─── SPF ──────────────────────────────────────────────────
phishdomain.com.          TXT     "v=spf1 a mx ip4:203.0.113.10 -all"

; ─── DKIM ─────────────────────────────────────────────────
mail._domainkey.phishdomain.com.  TXT  "v=DKIM1; h=sha256; k=rsa; p=MIIBIjAN..."

; ─── DMARC ────────────────────────────────────────────────
_dmarc.phishdomain.com.   TXT     "v=DMARC1; p=quarantine; rua=mailto:dmarc@phishdomain.com; adkim=r; aspf=r; pct=100"

; ─── CAA ──────────────────────────────────────────────────
phishdomain.com.          CAA  0 issue "letsencrypt.org"
phishdomain.com.          CAA  0 issuewild ";"
phishdomain.com.          CAA  0 iodef "mailto:security@your-real-domain.com"

; ─── SRV (mail service discovery) ─────────────────────────
_submission._tcp.phishdomain.com.  SRV  0 1 587 mail.phishdomain.com.
_imaps._tcp.phishdomain.com.       SRV  0 1 993 mail.phishdomain.com.
_imap._tcp.phishdomain.com.        SRV  0 0 0   .
_pop3._tcp.phishdomain.com.        SRV  0 0 0   .
_pop3s._tcp.phishdomain.com.       SRV  0 0 0   .

; ─── MTA-STS / TLSRPT ────────────────────────────────────
_mta-sts.phishdomain.com.         TXT  "v=STSv1; id=20260910"
_smtp._tls.phishdomain.com.       TXT  "v=TLSRPTv1; rua=mailto:tls-reports@your-real-domain.com"

Apply this template to every sending domain you operate.

Step 7: Import Campaign Templates

The HailBytes GoPhish Training Templates repository is an excellent starting point. It contains 91 email templates across 27 industries — from corporate HR and payroll lures to QR-code phishing (quishing) and SMS phishing (smishing) scenarios — plus matching landing pages and educational content.

Clone the Repository

cd /opt
sudo git clone https://github.com/HailBytes/gophish-training-templates.git

Import Email Templates

In the GoPhish admin panel at https://your-server-ip:3333:

The templates use GoPhish's template variables:

Import Landing Pages

Configure Sending Profiles

Create a separate sending profile for each domain you use. This lets you match the sender domain to the campaign theme — a financial lure from a finance-themed domain, an IT alert from a tech-themed domain.

Step 8: Building Effective Campaigns

Campaign Structure

The HailBytes repository suggests a progressive difficulty model:

Run quarterly baseline campaigns at Level 2 across the entire organisation. Run targeted campaigns at Level 3-4 for high-risk roles (finance, HR, IT admin).

User Groups

In GoPhish, create groups that match your organisation's structure:

John,Smith,john.smith@target-org.com,Finance Manager
Jane,Doe,jane.doe@target-org.com,HR Director

Segment groups by department so you can run department-specific campaigns and compare results.

Launching a Campaign

Staggering Delivery

GoPhish distributes emails evenly between the launch date and the send-by date. For a campaign of 200 users, setting a four-hour window means roughly one email per 72 seconds. This is sustainable for most receiving mail servers and avoids triggering volume-based blocks.

Step 9: Monitoring and Reporting

Real-Time Dashboard

The GoPhish dashboard shows:

Key Metrics to Track

The HailBytes documentation recommends these KPIs:

Exporting Results

GoPhish exports campaign results as CSV. Download from the campaign detail page and use them for management reporting. The CSV includes timestamps for every event (sent, opened, clicked, submitted), which lets you calculate time-to-click distributions.

Step 10: Hardening the Platform

Restrict GoPhish Admin Access

The admin panel on port 3333 should only be accessible from your management network:

sudo ufw delete allow 3333/tcp
sudo ufw allow from 198.51.100.0/24 to any port 3333

File Permissions

sudo chmod 600 /opt/gophish/config.json
sudo chmod 600 /opt/gophish/gophish.db

Log Rotation

Create /etc/logrotate.d/gophish:

/opt/gophish/gophish.log {
    weekly
    rotate 12
    compress
    delaycompress
    missingok
    notifempty
}

Database Backups

GoPhish uses SQLite by default. Back up the database daily:

sudo crontab -e
0 2 * * * cp /opt/gophish/gophish.db /var/backups/gophish-$(date +\%F).db

IP Reputation Monitoring

Check your server's IP reputation weekly. If it appears on a blacklist, stop all campaigns and request delisting before the reputation damage compounds:

# Check against major blacklists
dig +short 10.113.0.203.zen.spamhaus.org
dig +short 10.113.0.203.bl.spamcop.net
dig +short 10.113.0.203.b.barracudacentral.org

Replace 10.113.0.203 with your IP in reverse-octet order.

Step 11: Managing Multiple Sending Domains

For a portfolio of sending domains, each domain needs its own complete DNS configuration as described in Step 6. Automate the DKIM key generation:

#!/bin/bash
DOMAINS="phishdomain.com seconddomain.com thirddomain.com"

for DOMAIN in $DOMAINS; do
    sudo mkdir -p /etc/opendkim/keys/$DOMAIN
    sudo opendkim-genkey -b 2048 -d $DOMAIN \
        -D /etc/opendkim/keys/$DOMAIN -s mail -v
    echo "*@$DOMAIN    mail._domainkey.$DOMAIN" >> /etc/opendkim/signing.table
    echo "mail._domainkey.$DOMAIN    $DOMAIN:mail:/etc/opendkim/keys/$DOMAIN/mail.private" >> /etc/opendkim/key.table
done

sudo chown -R opendkim:opendkim /etc/opendkim
sudo systemctl restart opendkim
sudo systemctl restart postfix

Then publish each domain's DKIM public key to DNS. Display all of them:

for DOMAIN in phishdomain.com seconddomain.com thirddomain.com; do
    echo "=== $DOMAIN ==="
    cat /etc/opendkim/keys/$DOMAIN/mail.txt
    echo ""
done

Step 12: Validating Your Configuration

Before running your first campaign, validate every layer.

DNS

dig +short phishdomain.com A
dig +short phishdomain.com MX
dig +short phishdomain.com TXT
dig +short _dmarc.phishdomain.com TXT
dig +short mail._domainkey.phishdomain.com TXT
dig +short phishdomain.com CAA
dig +short _submission._tcp.phishdomain.com SRV
dig +short _mta-sts.phishdomain.com TXT

SPF and DKIM

Send a test message to check-auth@verifier.port25.com. The bounce-back report shows your SPF, DKIM and DMARC results.

Mail Flow

Send a test to a Gmail address and inspect the headers. Look for:

Authentication-Results:
  spf=pass
  dkim=pass
  dmarc=pass

All three should show pass. If any shows fail, fix it before launching a campaign.

TLS

openssl s_client -connect mail.phishdomain.com:25 -starttls smtp

Confirm the certificate chain is valid and the protocol is TLS 1.2 or higher.

Legal and Ethical Framework

Phishing simulation is authorised offensive testing. Without proper authorisation, it is a criminal offence in most jurisdictions.

Summary

The stack is GoPhish for campaign management, Postfix for delivery with rate limiting, Apache for TLS-terminated landing pages, and OpenDKIM for message signing. Every sending domain needs the full DNS suite — A, MX, PTR, SPF, DKIM, DMARC, CAA, SRV, MTA-STS and TLSRPT — configured before you send your first message. The HailBytes template repository gives you 91 ready-made email templates across 27 industries with matching landing pages and a progressive difficulty framework. Start at Level 1, establish a baseline, and work up. The platform delivers mail; the DNS makes it arrive; the templates make it convincing; and the metrics tell you whether your organisation is getting better.