My original setup used a DigitalOcean droplet, with DNS managed through GoDaddy. Getting the delegation right took longer than installing the server. This expanded guide brings back the configuration, certificate handling, and service commands from those notes, with corrections for a current installation.
Updated 12 October 2026. The examples below target a dedicated Ubuntu 24.04 server with systemd. They have been reviewed against vendor documentation, but have not been deployed to a fresh droplet as part of this revision.
1. Prepare the droplet
Use a dedicated machine: Collaborator needs ports commonly occupied by a web server or mail server. These examples use collab.example.com and the documentation address 203.0.113.53. Replace both everywhere. The sample administrator address, 198.51.100.10, must also be replaced with your actual public IP.
SSH to the droplet and install the runtime and diagnostic tools. PortSwigger currently requires Java 21 or newer for its standalone JAR. Check the requirement again when upgrading. Java requirements ↗
sudo apt-get update
sudo apt-get install -y openjdk-21-jre-headless dnsutils openssl
java -version
sudo useradd --system --user-group --home-dir /usr/local/collaborator --shell /usr/sbin/nologin collaborator
sudo install -d -o root -g collaborator -m 0750 /usr/local/collaborator
sudo install -d -o root -g collaborator -m 0750 /usr/local/collaborator/keys
If the service account already exists, keep it instead of running useradd again. Download the current Professional JAR from PortSwigger’s release page, transfer it to the droplet, and install it at the single path used throughout this guide:
# Run on the droplet after uploading the downloaded JAR as ~/burp.jar:
sudo install -o root -g collaborator -m 0640 ~/burp.jar /usr/local/collaborator/burp.jar
sudo ss -lntup
Inspect the listeners before proceeding. A WordPress droplet already serving Apache or Nginx on 80/443 is not the dedicated host assumed here. Bind Collaborator to the droplet’s actual interface address; a public address supplied only through NAT cannot be used as a local bind address.
Apply the following inbound rules in both the cloud firewall and any host firewall. Preserve your SSH access before enabling a firewall. The interaction ports follow PortSwigger’s server setup guide.
| Purpose | Inbound ports | Source |
|---|---|---|
| SSH administration | TCP 22 | Your administrator IP/VPN |
| DNS interactions | UDP 53 | Target resolvers; internet-wide for public tests |
| Web interactions | TCP 80, 443 | Target systems; internet-wide for public tests |
| Mail interactions | TCP 25, 587, 465 | Target systems; internet-wide if required |
| HTTPS polling | TCP 39443 | Your tester IP/VPN |
Our configuration also binds HTTP polling to 39090; leave that port blocked externally and use HTTPS. A firewall rule does not override provider-level restrictions. Validate the protocols you intend to use from outside the droplet.
2. Issue and install certificates
For a new subdomain, obtain the first certificate before adding the Collaborator NS delegation. At this stage your ordinary DNS provider can answer the ACME TXT challenge. If the subdomain is already delegated, use the renewal procedure below instead; adding TXT records only at the parent provider will not work.
Install Certbot using its current instructions for your OS. Request the base name and wildcard together; quote the wildcard to prevent shell expansion:
sudo certbot certonly --manual --preferred-challenges dns --key-type rsa --cert-name collab.example.com -d collab.example.com -d '*.collab.example.com'
Follow each prompt and publish the exact TXT values it requests at _acme-challenge.collab.example.com. Your DNS panel may want the relative name _acme-challenge.collab. If two challenges require values at the same name, retain both until validation finishes. Verify each requested value before continuing:
dig TXT _acme-challenge.collab.example.com @1.1.1.1 +short
sudo certbot certificates
Use the certificate path reported by Certbot. Manual DNS validation requires manual renewal unless authentication hooks are added. Certbot manual mode ↗
Save the following script as /usr/local/collaborator/configure_certs.sh. It fixes the original script’s Cfi typo, checks its input, converts the key to PKCS#8, and installs the leaf and intermediate chain separately.
#!/bin/bash
set -euo pipefail
cert_name=${1:?Usage: configure_certs.sh CERTIFICATE_NAME}
[[ "$cert_name" =~ ^[A-Za-z0-9.-]+$ ]] || { echo "Invalid certificate name" >&2; exit 1; }
cert_dir="/etc/letsencrypt/live/$cert_name"
key_dir=/usr/local/collaborator/keys
for file in privkey.pem cert.pem chain.pem; do
test -s "$cert_dir/$file" || { echo "Missing $cert_dir/$file" >&2; exit 1; }
done
umask 077
staging_dir=$(mktemp -d)
trap 'rm -rf "$staging_dir"' EXIT
openssl pkcs8 -topk8 -nocrypt -in "$cert_dir/privkey.pem" -out "$staging_dir/privkey.pkcs8.pem"
install -o root -g collaborator -m 0640 "$staging_dir/privkey.pkcs8.pem" "$key_dir/privkey.pkcs8.pem"
install -o root -g collaborator -m 0640 "$cert_dir/cert.pem" "$key_dir/cert.pem"
install -o root -g collaborator -m 0640 "$cert_dir/chain.pem" "$key_dir/chain.pem"
echo "Certificates installed. Restart Collaborator to load them."
sudo chown root:root /usr/local/collaborator/configure_certs.sh
sudo chmod 0750 /usr/local/collaborator/configure_certs.sh
sudo /usr/local/collaborator/configure_certs.sh collab.example.com
3. Write the configuration
Save this JSON as /usr/local/collaborator/collaborator.config. It retains the original separate polling ports. Substitute your domain and addresses first. For a directly addressed droplet, the local and public addresses are the same; check with ip -br address.
{
"serverDomain": "collab.example.com",
"workerThreads": 10,
"eventCapture": {
"localAddress": [
"203.0.113.53"
],
"publicAddress": "203.0.113.53",
"http": {
"ports": 80
},
"https": {
"ports": 443
},
"smtp": {
"ports": [
25,
587
]
},
"smtps": {
"ports": 465
},
"ssl": {
"certificateFiles": [
"/usr/local/collaborator/keys/privkey.pkcs8.pem",
"/usr/local/collaborator/keys/cert.pem",
"/usr/local/collaborator/keys/chain.pem"
]
}
},
"polling": {
"localAddress": "203.0.113.53",
"publicAddress": "203.0.113.53",
"http": {
"port": 39090
},
"https": {
"port": 39443
},
"ssl": {
"certificateFiles": [
"/usr/local/collaborator/keys/privkey.pkcs8.pem",
"/usr/local/collaborator/keys/cert.pem",
"/usr/local/collaborator/keys/chain.pem"
]
}
},
"dns": {
"interfaces": [
{
"name": "ns1",
"localAddress": "203.0.113.53",
"publicAddress": "203.0.113.53"
}
],
"ports": 53
},
"logLevel": "INFO"
}
Two easy-to-miss details: dns.interfaces.name is ns1, not the fully qualified name; Collaborator appends the domain. Interaction listeners use ports, but polling uses port. Configuration field reference ↗
The certificate list is key, leaf, then intermediates. Using cert.pem followed by fullchain.pem repeats the leaf certificate. This version uses chain.pem. TLS configuration ↗
sudo chown root:collaborator /usr/local/collaborator/collaborator.config
sudo chmod 0640 /usr/local/collaborator/collaborator.config
Metrics are optional and omitted above. If you add a top-level metrics object, generate a secret path using openssl rand -hex 16 and restrict addressWhitelist to your administrator IP, for example ["198.51.100.10/32"]. The original 0.0.0.0/1 covers half the IPv4 space. Keep the polling firewall restriction as well.
4. Run it under systemd
Save this unit at /etc/systemd/system/collaborator.service. The service account was created earlier; the capability permits low-port binding without running the whole JVM as root. The heap limit below is a starting point for a small deployment, not a sizing guarantee. Leave RAM for the OS and JVM overhead.
[Unit]
Description=Michaelis Labs Burp Collaborator
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=collaborator
Group=collaborator
WorkingDirectory=/usr/local/collaborator
ExecStart=/usr/bin/java -Xms64m -Xmx512m -jar /usr/local/collaborator/burp.jar --collaborator-server --collaborator-config=/usr/local/collaborator/collaborator.config
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
PrivateTmp=true
UMask=0077
Restart=on-failure
RestartSec=5
TimeoutStopSec=60
[Install]
WantedBy=multi-user.target
See systemd’s execution documentation for capability and privilege semantics. Start the service, inspect its journal, and only enable boot startup after it stays healthy:
sudo systemd-analyze verify /etc/systemd/system/collaborator.service
sudo systemctl daemon-reload
sudo systemctl start collaborator
sudo systemctl status collaborator --no-pager
sudo journalctl -u collaborator -n 80 --no-pager
sudo ss -lntup
# After confirming successful startup:
sudo systemctl enable collaborator
After a JSON or certificate change, use sudo systemctl restart collaborator. After a unit-file edit, run daemon-reload first, then restart. Disabling and re-enabling the service is unnecessary. Treat restarts as a maintenance event and collect outstanding interactions beforehand.
5. Delegate the subdomain and check resolution
At the provider authoritative for example.com, add these records. Names shown here are relative to that zone; some panels require complete names instead. Set up address records before the delegation.
| Type | Name | Value |
|---|---|---|
| A | collab | 203.0.113.53 |
| A | ns1.collab | 203.0.113.53 |
| NS | collab | ns1.collab.example.com. |
The nameserver sits beneath the zone it serves, so the parent must supply its address as glue. Check your provider’s delegation workflow if it will not accept this arrangement. Keep existing records for the company website and mail in place. Do not publish an AAAA record unless IPv6 is actually configured and reachable.
# Direct query: verifies the service, not the public delegation.
dig @203.0.113.53 probe.collab.example.com A
# Public resolver: verifies the path a remote application uses.
dig @1.1.1.1 probe.collab.example.com A
dig @1.1.1.1 collab.example.com NS
dig @1.1.1.1 ns1.collab.example.com A
# Trace the delegation when the two paths disagree.
dig +trace probe.collab.example.com A
The A queries should return your interaction IP. The trace should reach your Collaborator nameserver. A successful direct query with failed public resolution points toward delegation, glue, caching, or DNSSEC rather than the application listener. Use a fresh probe label after changes to reduce confusion from cached answers.
6. Connect Burp and test a real callback
In Burp’s Settings → Project → Collaborator, choose the private server option. Use collab.example.com for the server location and, for this split-port setup, configure the polling location as collab.example.com:39443 with HTTPS. Collaborator prepends polling. when polling; the wildcard certificate covers that hostname. Run the built-in health check. Private-server connection settings ↗
Check the TLS endpoint from your allowed tester machine:
openssl s_client -connect polling.collab.example.com:39443 -servername polling.collab.example.com -verify_hostname polling.collab.example.com -verify_return_error </dev/null
Then generate a fresh payload in Burp’s Collaborator client. Replace the example below with that exact hostname, trigger a request from a separate machine, and poll for its interactions:
dig YOUR_GENERATED_ID.collab.example.com
curl -v https://YOUR_GENERATED_ID.collab.example.com/michaelis-check
Match the generated identifier and timestamp in Burp. An arbitrary DNS probe validates routing, but is not a payload your Burp client necessarily owns. Do not use curl -k to claim TLS is working: it bypasses certificate verification. If you need SMTP interactions, verify those health-check results too.
7. Make certificate renewal part of the setup
Once the NS delegation is live, the parent provider’s TXT records beneath collab.example.com are no longer authoritative. That is why repeating the initial certificate instructions at GoDaddy can fail.
For manual renewal, repeat the Certbot issuance command. While it waits, add its current challenge values to a top-level customDnsRecords array in the Collaborator JSON. Include only the values actually requested:
"customDnsRecords": [
{"label": "_acme-challenge", "type": "TXT", "record": "FIRST_CURRENT_TOKEN", "ttl": 60},
{"label": "_acme-challenge", "type": "TXT", "record": "SECOND_CURRENT_TOKEN_IF_REQUESTED", "ttl": 60}
]
This is a field to merge into the existing object, not a replacement configuration. Restart to load the records, confirm the requested TXT answers through a public resolver, and continue Certbot. After issuance, run the certificate-install script and restart again. Remove obsolete challenge values and reload during the same maintenance window. Custom challenge records ↗
dig @1.1.1.1 TXT _acme-challenge.collab.example.com +short
# After successful issuance:
sudo /usr/local/collaborator/configure_certs.sh collab.example.com
sudo systemctl restart collaborator
sudo openssl x509 -in /usr/local/collaborator/keys/cert.pem -noout -dates
For unattended operation, implement and test DNS authentication hooks or a challenge delegation to an API-managed zone, plus a deploy hook that installs the renewed files and restarts the service. A renewal timer alone cannot complete interactive DNS prompts. Monitor the certificate actually served by Collaborator as well as the files on disk. Renewal and deploy hooks ↗
8. Troubleshoot the failing layer
- Process exits immediately: inspect the journal for JSON errors, an unsupported Java version, unreadable certificate files, or an occupied port.
- Cannot bind the requested address: compare
localAddresswithip -br address; distinguish the interface address from its public NAT address. - Direct DNS works, public DNS fails: inspect the parent delegation and glue with
dig +trace; check any DNSSEC configuration and cached failures. - DNS works, HTTPS fails: check 443 reachability, certificate names and expiry, and the intermediate chain.
- Callbacks arrive but Burp cannot poll: check the tester’s public IP against firewall rules, port 39443, and the polling hostname’s certificate.
- Renewal succeeds but TLS remains expired: copy the new certificate material into the configured paths, restart, and check the served certificate again.
The setup is complete when the service survives a restart, public resolution reaches it, TLS validates, and a payload generated by your own Burp client produces a retrievable interaction. Record the domain, service owner, certificate renewal method, and maintenance procedure alongside the configuration.