Run a webhook collector on a public VM
Some sources push events to the collector over HTTP instead of being scraped or polled. For those webhook-based receivers, the collector must expose an endpoint that the sending service can reach over the public internet, secured with TLS.
This guide covers the infrastructure you prepare around the collector so a webhook endpoint is reachable and trusted:
- a public, stable address — a domain name pointing at a static public IP, and
- HTTPS with a certificate the sending service trusts.
The collector software itself is installed using the platform-specific guides — see Linux, Windows, macOS, or Kubernetes. This page focuses on the networking and certificate work that makes a push endpoint safe to expose.
How a webhook receiver works
push events (HTTPS, POST)
External service ───────────────────────────────────▶ collector.example.com
(sends webhooks) (your public VM)
│
▼
webhook receiver
(listens on a TLS port)
To get there you will:
- Provision a public VM with a static public IP.
- Point a domain name at that IP (DNS A record).
- Open the required firewall ports.
- Obtain a TLS certificate for the domain.
- Install the collector and point the receiver at your certificate.
- Verify the endpoint is reachable and healthy.
Prerequisites
| Requirement | Why it's needed |
|---|---|
| A public VM (any cloud or hosting provider) | Hosts the collector and must be reachable from the internet. |
| A static / reserved public IP | The DNS record must point at an address that does not change on reboot. |
| A domain or subdomain you control | Webhook senders connect to a hostname; certificates are issued against it. |
| Ability to edit DNS records for that domain | To create the A record and, for ACME, validation records. |
| Inbound firewall access to the listen port | So the external service can reach the collector. |
| Root / sudo on the VM | To open ports, install certificates, and run the collector service. |
A modern 64-bit Linux VM with 2 vCPU / 4 GB RAM is a reasonable starting point, scaled to expected event volume.
1. Point a domain at the VM (DNS)
Webhook providers connect to a hostname, not a raw IP, and TLS certificates are issued for hostnames. Create a DNS A record for a subdomain that points to the VM's static public IP.
| Field | Example value |
|---|---|
| Type | A |
| Name / Host | collector (becomes collector.example.com) |
| Value / Target | 203.0.113.10 (your VM's static public IP) |
| TTL | 300 (5 minutes) while setting up |
Verify the record resolves before continuing:
dig +short collector.example.com
# should print your VM's public IP
Use a low TTL during setup so DNS changes take effect quickly.
2. Open firewall ports
Open inbound access to the port the webhook endpoint will listen on. Use a dedicated HTTPS port.
| Port | Purpose | Required |
|---|---|---|
Your chosen HTTPS port (e.g. 443) | Inbound webhook traffic over TLS | Yes |
80 (HTTP) | Let's Encrypt HTTP-01 validation / renewal only | Only if using ACME HTTP-01 |
Open the ports in both places where applicable:
- the cloud security group / network ACL (provider firewall), and
- the host firewall on the VM, for example:
# ufw example
sudo ufw allow 443/tcp
sudo ufw allow 80/tcp # only if using Let's Encrypt HTTP-01
:::tip Restrict the source range If the sending service publishes a fixed set of egress IP ranges, allow only those ranges to reach your webhook port. :::
3. Obtain a TLS certificate
The receiver serves HTTPS using a certificate file, a private key file, and optionally a CA / chain file (used only when you verify client certificates, i.e. mTLS). Choose one of the approaches below.
You will end up with:
cert_file— the server certificate (usually the full chain: leaf + intermediates),key_file— the matching private key,ca_file— (optional) a CA bundle for client-certificate (mTLS) verification.
Option A — Let's Encrypt (free, automated, public CA)
Best when the domain is publicly resolvable. Certificates auto-renew.
# Install certbot (package name varies by distro)
sudo apt-get update && sudo apt-get install -y certbot
# HTTP-01 validation (requires inbound port 80 open to this VM)
sudo certbot certonly --standalone -d collector.example.com
Issued files (default paths):
Certificate (chain): /etc/letsencrypt/live/collector.example.com/fullchain.pem
Private key: /etc/letsencrypt/live/collector.example.com/privkey.pem
Certbot installs a renewal timer automatically. Confirm with:
sudo certbot renew --dry-run
:::tip No inbound port 80?
Use DNS-01 validation (certbot ... --preferred-challenges dns), which proves domain ownership via a DNS TXT record and works behind closed inbound ports.
:::
Option B — Bring your own / commercial CA certificate
Best when your organization already has certificates from a commercial or internal CA.
-
Generate a private key and a Certificate Signing Request (CSR):
openssl req -new -newkey rsa:2048 -nodes \-keyout collector.example.com.key \-out collector.example.com.csr \-subj "/CN=collector.example.com" -
Submit the CSR to your CA and complete their validation. They return a signed certificate plus intermediate/chain certificates.
-
Build the full chain the server presents (leaf first, then intermediates):
cat collector.example.com.crt intermediate.crt > fullchain.pem
You now have fullchain.pem (certificate chain) and collector.example.com.key (private key).
:::caution Protect the private key
Restrict permissions (chmod 600), never commit it to source control, and never share it with the webhook sender — the sender only needs to trust the CA, not hold your key.
:::
:::note Internal / private PKI If you are issuing certificates from your own private CA with OpenSSL, the TLS and certificates guide walks through creating a CA, server, and client certificates. The same PEM artifacts apply here. :::
Common certificate handling
-
Place certificate and key files in a stable directory the collector can read, for example
/etc/collector/tls/, owned by the user the collector runs as:sudo mkdir -p /etc/collector/tlssudo chmod 700 /etc/collector/tlssudo chmod 600 /etc/collector/tls/*.pem -
The certificate's Common Name (CN) or Subject Alternative Name (SAN) must match the domain in your DNS record (
collector.example.com).
4. Configure the webhook receiver
Install the collector with the guide for your platform, then configure the webhook receiver to use:
- Listen endpoint — bind to all interfaces on your chosen HTTPS port, e.g.
0.0.0.0:443. - TLS settings — point at the files from step 3:
- certificate / chain file (
fullchain.pem), - private key file (
privkey.pemor your.key), - CA file (only if you require client-certificate / mTLS authentication).
- certificate / chain file (
- Path — the route the sender POSTs to (for example
/events). Configure the sending service to usehttps://collector.example.com/<path>. - Authentication — enable the authentication the receiver supports (shared token / HMAC signature, or client certificates) so the endpoint never accepts anonymous writes.
:::caution Binding privileged ports
If the collector runs as a non-root user it cannot bind to ports below 1024 (like 443) by default. Either grant the capability (sudo setcap 'cap_net_bind_service=+ep' <collector-binary>), front it with a reverse proxy, or use a high port (e.g. 8443) and open that port in the firewall instead.
:::
5. Verify the endpoint
From a machine outside the VM, confirm TLS and reachability:
# Check the certificate is served and valid for the domain
openssl s_client -connect collector.example.com:443 -servername collector.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -dates
# Hit the health endpoint (path depends on receiver config)
curl -i https://collector.example.com/health_check
Send a test event to the configured path (include the auth headers your receiver expects):
curl -i -X POST https://collector.example.com/events \
-H "Content-Type: application/json" \
-d '{"test":"event"}'
A clean TLS handshake (no certificate warnings) plus a 2xx response confirms the endpoint is ready. Then point the external service at https://collector.example.com/<path>.
6. Renew and rotate certificates
- Let's Encrypt: renewal is automatic via the certbot timer (certificates last ~90 days). After renewal the collector must reload the new files — restart the collector service, or use a renewal hook, so it picks up the rotated certificate.
- Commercial / internal CA: track the expiry date and repeat step 3 (Option B) before it lapses. Replace the files in place and restart the collector.
Check the live expiry any time:
echo | openssl s_client -connect collector.example.com:443 -servername collector.example.com 2>/dev/null \
| openssl x509 -noout -enddate
Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
| Connection times out | Firewall/security group not open, or wrong port | Verify the cloud firewall and host firewall allow the listen port; confirm the collector is listening (ss -tlnp). |
certificate name mismatch | Cert CN/SAN ≠ domain | Reissue the certificate for the exact hostname in your DNS record. |
unable to verify the first certificate | Incomplete chain | Serve the full chain (leaf + intermediates), not just the leaf certificate. |
| DNS doesn't resolve | A record missing or not propagated | dig +short collector.example.com; wait for TTL; confirm it points to the static IP. |
| Sender reports 401/403 | Authentication misconfigured | Confirm the sender includes the expected token / signature or client certificate. |
| Collector can't bind port 443 | Non-root process binding a privileged port | Use setcap, a high port, or a reverse proxy (see step 4). |
| Let's Encrypt validation fails | Port 80 blocked or DNS not pointing here | Open port 80 for HTTP-01, or switch to DNS-01 validation. |
Security checklist
- Private key stored with
600permissions, never committed or shared. - Endpoint requires authentication (token / HMAC or client certificate) — no anonymous writes.
- Firewall restricts the webhook port to the sender's IP ranges where possible.
- TLS minimum version set to a modern baseline (TLS 1.2+).
- Certificate auto-renewal (or an expiry reminder) is in place.
- Only the required ports are exposed publicly.