# GoPhish Installation and Phishing Simulation Setup

GoPhish is an open-source phishing toolkit for businesses and penetration testers. It lets you build email templates and landing pages, send campaigns to groups of users, and track who opened, clicked, submitted data or reported the email. It ships as a single Go binary with an embedded SQLite database, so it is easy to deploy and manage.

The result of this guide:

- GoPhish runs on a **local machine** (a lab server, VM or mini PC) with no public IP and **no inbound port** opened on your firewall.
- The **phishing listener** is published on a dedicated domain through a **Cloudflare Tunnel**; TLS is terminated at Cloudflare's edge.
- The **admin UI** listens only on localhost and is never published.
- Emails go out through an authenticated SMTP relay (Namecheap PrivateEmail in this example), with SPF/DKIM/DMARC aligned and the simulation **allowlisted** on the target's mail gateway.

**Note:** Run phishing simulations only with written authorization from the organization that owns the target mailboxes (scope, dates, target list, what data may be collected). Never collect real passwords: configure landing pages to capture the click and form submission only.

# Architecture

![](https://cdn.hashnode.com/uploads/covers/64bf2d23abab30ccbe8ab635/6b8617d0-f6d3-4e74-8ad8-15205409d6c7.png align="center")

The local machine opens no inbound port: `cloudflared` keeps an outbound connection to Cloudflare, which serves the landing page over TLS. Emails leave through the SMTP relay, and the admin UI never leaves localhost.

## Installation Environment and Versions

- Local machine: Ubuntu 24.04 LTS, 2 vCPU, 2 GB RAM, always on during the campaign (no laptop that goes to sleep)
- Domain: registered at Namecheap, DNS hosted on **Cloudflare** (Free plan is enough)
- Publishing: `cloudflared` (Cloudflare Tunnel)
- Mail: Namecheap PrivateEmail SMTP (any authenticated relay works: Mailgun, Amazon SES, Microsoft 365)
- GoPhish: v0.12.1 ([releases](https://github.com/gophish/gophish/releases))

| **Item** | **Example value** |
| --- | --- |
| Local machine | `gophish-lab` (192.168.1.50, behind NAT) |
| Phishing domain | `secure-portal.example.net` (Cloudflare Tunnel) |
| Sender mailbox | `it-notice@example.net` (Namecheap PrivateEmail) |
| Admin UI | `127.0.0.1:3333` (local only, never published) |
| Phishing listener | `127.0.0.1:8080` (only reachable through the tunnel) |

Use a domain you control and that is clearly yours. Do not register look-alike domains of a real third-party brand.

## Move the Domain DNS to Cloudflare

Cloudflare Tunnel needs the domain's DNS on Cloudflare. Add the domain in the Cloudflare dashboard (Free plan), then in Namecheap → Domain List → **Manage** → **Nameservers** choose **Custom DNS** and enter the two nameservers Cloudflare assigned. Wait until the domain shows **Active** in Cloudflare.

The local machine only needs **outbound** access to Cloudflare (TCP/UDP 7844 for the tunnel, 443 for the API), plus outbound 465 or 587 to the SMTP relay. Nothing is opened inbound on your router or firewall.

**Note:** Cloudflare actively scans for phishing on its network and may show a warning page or suspend the hostname, even for an authorized simulation. Keep the written authorization at hand, use your own organization's branding (never a third-party brand), keep campaigns short, and remove the hostname when the campaign ends.

# Download and Install GoPhish

Create a dedicated user and install the binary under `/opt/gophish`:

```bash
sudo apt-get update && sudo apt-get install -y unzip
sudo useradd -r -m -d /opt/gophish -s /usr/sbin/nologin gophish

cd /tmp
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 /opt/gophish
sudo chmod +x /opt/gophish/gophish
sudo chown -R gophish:gophish /opt/gophish
```

**Note:** Check the release page for checksums and a newer version before you install. The asset name format above matches v0.12.1.

### GoPhish Configuration

Configure config.json

Bind both listeners to localhost: the admin UI stays private, and cloudflared publishes only the phishing listener.

```bash
sudo -u gophish vim /opt/gophish/config.json
```

```json
{
  "admin_server": {
    "listen_url": "127.0.0.1:3333",
    "use_tls": true,
    "cert_path": "gophish_admin.crt",
    "key_path": "gophish_admin.key",
    "trusted_origins": []
  },
  "phish_server": {
    "listen_url": "127.0.0.1:8080",
    "use_tls": false,
    "cert_path": "example.crt",
    "key_path": "example.key"
  },
  "db_name": "sqlite3",
  "db_path": "gophish.db",
  "migrations_prefix": "db/db_",
  "contact_address": "security-awareness@example.net",
  "logging": {
    "filename": "",
    "level": ""
  }
}
```

- `contact_address` is added to every email as the `X-Gophish-Contact` header, so a recipient or mail admin can report the simulation.
- `trusted_origins` (new in v0.12.1) is only needed if you later publish the admin UI behind a reverse proxy on another hostname.
- The admin certificate is self-signed and generated automatically on first start.

## Run as a systemd Service

GoPhish uses relative paths, so `WorkingDirectory` is required:

```bash
sudo tee /etc/systemd/system/gophish.service > /dev/null <<'EOF'
[Unit]
Description=GoPhish phishing simulation
After=network-online.target

[Service]
Type=simple
User=gophish
Group=gophish
WorkingDirectory=/opt/gophish
ExecStart=/opt/gophish/gophish
Restart=on-failure
NoNewPrivileges=true
PrivateTmp=true

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

Since v0.11 there is no default password. The initial admin password is generated randomly and printed in the log on first launch:

```bash
sudo journalctl -u gophish | grep -i "password"
==============================================================================
... Please login with the username admin and the password <random_password>
```

## Access the Admin UI

On the local machine (or from another host on the LAN through `ssh -L 3333:127.0.0.1:3333 user@192.168.1.50`), browse to `https://127.0.0.1:3333` and accept the self-signed certificate.

![](https://cdn.hashnode.com/uploads/covers/64bf2d23abab30ccbe8ab635/7cbd2787-888d-4c81-8438-2dcaf20fe872.png align="center")

Log in with `admin` and the generated password. GoPhish forces a password change at first login. Then create one account per operator under **User Management** instead of sharing `admin`.

![](https://cdn.hashnode.com/uploads/covers/64bf2d23abab30ccbe8ab635/7e7ea050-06df-4f1c-bbd0-3bb4e56599ba.png align="center")

## Publish the Phishing Listener with Cloudflare Tunnel

Install `cloudflared` from Cloudflare's APT repository:

```bash
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg > /dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install -y cloudflared
```

Authenticate, create a named tunnel and route the hostname to it (this creates a proxied CNAME in Cloudflare DNS):

```bash
cloudflared tunnel login                      # opens a browser, pick example.net
cloudflared tunnel create gophish
==============================================================================
Created tunnel gophish with id <TUNNEL_ID>
cloudflared tunnel route dns gophish secure-portal.example.net
```

Create the tunnel configuration. Only the phishing listener is published; everything else returns 404:

```bash
sudo mkdir -p /etc/cloudflared
sudo cp ~/.cloudflared/<TUNNEL_ID>.json /etc/cloudflared/
sudo tee /etc/cloudflared/config.yml > /dev/null <<'EOF'
tunnel: <TUNNEL_ID>
credentials-file: /etc/cloudflared/<TUNNEL_ID>.json

ingress:
  - hostname: secure-portal.example.net
    service: http://127.0.0.1:8080
  - service: http_status:404
EOF
```

Run it as a service and verify:

```bash
sudo cloudflared service install
sudo systemctl enable --now cloudflared
cloudflared tunnel info gophish
curl -sI https://secure-portal.example.net | head -1
```

In the Cloudflare dashboard, set **SSL/TLS → Edge Certificates → Always Use HTTPS** to on. The origin stays plain HTTP on `127.0.0.1:8080`, which never leaves the machine.

**Note:** Never add `127.0.0.1:3333` to the ingress rules. If remote admin access is really needed, publish it on a separate hostname protected by **Cloudflare Access** (SSO/OTP policy), and add that hostname to `trusted_origins` in `config.json`, otherwise GoPhish rejects the login with a CSRF error.

**Note:** Behind the tunnel, GoPhish may record `127.0.0.1` as the client IP. Cloudflare sends the real address in `CF-Connecting-IP` and `X-Forwarded-For`; check what your GoPhish version stores after the first test campaign.

# Mail Delivery

DNS is where most phishing simulations fail: if SPF, DKIM and DMARC are wrong, the emails never reach the inbox and the campaign measures your mail gateway instead of your users.

## DNS Records on Cloudflare

Since the nameservers now point to Cloudflare, mail records are added in Cloudflare → **DNS** → **Records** (not in Namecheap Advanced DNS anymore). The CNAME for the landing page was created by `cloudflared tunnel route dns`. Copy the exact PrivateEmail values from your PrivateEmail panel. Mail records must be **DNS only** (grey cloud).

| **Type** | **Name** | **Value** | **Purpose** |
| --- | --- | --- | --- |
| CNAME (proxied) | `secure-portal` | `<TUNNEL_ID>.cfargotunnel.com` | Landing page through the tunnel (auto-created) |
| MX | `@` | `mx1.privateemail.com` / `mx2.privateemail.com` | Mailbox for replies and bounces |
| TXT | `@` | `v=spf1 include:spf.privateemail.com ~all` | SPF: who may send for the domain |
| TXT | `default._domainkey` | *(key from PrivateEmail → DKIM)* | DKIM signature |
| TXT | `_dmarc` | `v=DMARC1; p=none; rua=mailto:dmarc@example.net; fo=1` | DMARC monitoring and reports |

Check propagation before going further:

```bash
dig +short secure-portal.example.net          # Cloudflare edge IPs (proxied)
dig +short MX example.net
dig +short TXT example.net
dig +short TXT default._domainkey.example.net
dig +short TXT _dmarc.example.net
```

**Note:** Use a new domain for the simulation, not your production domain, and let it age a few days before the first campaign. Brand-new domains are often flagged by mail and web filters.

## Sending Profile

In GoPhish → **Sending Profiles** → **New Profile**:

| **Field** | **Value** |
| --- | --- |
| Name | `PrivateEmail - it-notice` |
| From | `IT Service Desk <it-notice@example.net>` |
| Host | `mail.privateemail.com:465` (or `:587` with STARTTLS) |
| Username / Password | PrivateEmail mailbox credentials |
| Ignore Certificate Errors | Unchecked |

The **From** address must be on the same domain that SPF and DKIM cover, otherwise DMARC alignment fails. Click **Send Test Email** to your own mailbox and check the headers: `spf=pass`, `dkim=pass`, `dmarc=pass`.

## Allowlist the Simulation on the Target Mail Gateway

For an internal awareness program, the goal is to test **people**, not to bypass the email filters. Ask the mail administrator of the target organization to allowlist the simulation, for example:

- **Microsoft 365:** Defender portal → Email & collaboration → Policies → **Advanced delivery** → Phishing simulation (sending domain, sending IP of the relay, simulation URL).
- **Google Workspace:** an inbound gateway / spam allowlist rule for the sending domain or IP.
- **On-premise gateways** (FortiMail, Proofpoint, Cisco...): bypass rule for the sending IP and domain, and URL rewriting / sandbox exclusion for the phishing domain, so automated link scanners do not create false clicks.

Keep the `X-Gophish-Contact` header. It lets SOC analysts identify the simulation quickly if a user reports the email.

# Run a Campaign

A campaign combines five objects. Create them in this order in the admin UI.

## 1\. Users & Groups

**Users & Groups** → **New Group**, then add users manually or import a CSV with this header:

```csv
First Name,Last Name,Email,Position
An,Nguyen,an.nguyen@target.example,Accountant
Binh,Tran,binh.tran@target.example,HR Officer
```

Start with a small pilot group (IT and security staff) to validate delivery and tracking before the real target list.

## 2\. Email Templates

**Email Templates** → **New Template**: set a name, subject and HTML body. The important template variables are:

![](https://cdn.hashnode.com/uploads/covers/64bf2d23abab30ccbe8ab635/29609cef-e2d2-4063-9527-fb9b2222440e.png align="center")

| **Variable** | **Meaning** |
| --- | --- |
| `{{.URL}}` | Unique tracking link for each recipient (required) |
| `{{.Tracker}}` | Invisible image that records the "Email Opened" event |
| `{{.FirstName}}`, `{{.LastName}}`, `{{.Position}}` | Personalization from the group CSV |

Keep **Add Tracking Image** enabled. Build scenarios that match your training goals (password expiry, shared document, delivery notice) using your own organization's context, not a third-party brand.

## 3\. Landing Pages

**Landing Pages** → **New Page**, paste your HTML or use **Import Site** on a page you own.

- Enable **Capture Submitted Data** to record *that* a user submitted the form.
- Leave **Capture Passwords** **disabled**. GoPhish then stores the event without the password value, which is enough to measure behavior and avoids holding real credentials.
- Set **Redirect to** an internal awareness page that explains the warning signs the user missed.

![](https://cdn.hashnode.com/uploads/covers/64bf2d23abab30ccbe8ab635/83669d30-eab2-4b66-901b-9f32e77769a0.png align="center")

## 4\. Sending Profile

Use the profile created in *Mail Delivery*.

![](https://cdn.hashnode.com/uploads/covers/64bf2d23abab30ccbe8ab635/23365645-1f37-45e6-a0ca-3db848045866.png align="center")

## 5\. Campaigns

**Campaigns** → **New Campaign**:

![](https://cdn.hashnode.com/uploads/covers/64bf2d23abab30ccbe8ab635/8dc0b574-5ac1-4808-954e-4c694161e460.png align="center")

| **Field** | **Value** |
| --- | --- |
| Email Template / Landing Page / Sending Profile | The objects created above |
| URL | `https://secure-portal.example.net` |
| Launch Date | Agreed start time |
| Send Emails By | Spread over 1–2 hours to avoid rate limits and bursts |
| Groups | Target group |

Click **Launch Campaign**.

## Read the Results

The campaign page shows a timeline per recipient and these counters:

| **Event** | **What it tells you** |
| --- | --- |
| Email Sent | Delivery attempt accepted by the relay |
| Email Opened | Tracking image loaded (undercounted when images are blocked) |
| Clicked Link | User opened `{{.URL}}` |
| Submitted Data | User submitted the landing page form |
| Email Reported | User reported the email (via a report button integration calling GoPhish's report endpoint) |

The most useful awareness metrics are the **click rate**, the **submit rate** and the **report rate**. Export results as CSV (**Export CSV** on the campaign page) for the report, and treat them as personal data.

## Conclusion

I introduced a way to run GoPhish on a local machine and publish only its landing pages through a Cloudflare Tunnel, without a cloud VM, a public IP or any inbound firewall rule. Combined with aligned SPF/DKIM/DMARC and an allowlist on the mail gateway, the campaign measures what matters: how users react to a phishing email. Hope you find it helpful!
