Admin – Configure Domains and TLS
Dmitrii Iurco edited this page 2026-06-11 15:39:28 -04:00

Status: Active | Owner: @roof | Applies to: main (2026-06) | Updated: 2026-06-11

Admin – Configure Domains and TLS

PIC supports five domain modes. The mode determines how your cell gets an HTTPS certificate and whether it registers a public DNS name.


Domain modes

Mode Certificate Use case
pic_ngo Wildcard Let's Encrypt via DNS-01; subdomain <cell-name>.pic.ngo Internet-facing; recommended for most users
cloudflare Wildcard Let's Encrypt via Cloudflare DNS-01 Bring-your-own domain with Cloudflare DNS
duckdns Let's Encrypt via DuckDNS DNS-01 Bring-your-own DuckDNS subdomain
http01 Let's Encrypt per-subdomain cert via HTTP-01 Cell reachable on port 80; no wildcard cert
lan Internal CA only LAN-only; no internet required

pic_ngo

PIC registers <cell-name>.pic.ngo with the ddns.pic.ngo service during setup. A background thread re-checks and re-publishes your public IP every 5 minutes. The DDNS service issues a wildcard Let's Encrypt certificate via DNS-01 challenge, so all subdomains (mail.*, calendar.*, files.*, and others) are covered by one cert.

Requirements:

  • Accurate host clock — see Host NTP below
  • Ports 80 and 443 reachable from the internet (for the ACME challenge and for clients)

Registration uses a time-based one-time password (TOTP) to prevent unauthorised subdomain registration on pic.ngo. This is why accurate host time is required.

cloudflare

You provide a Cloudflare API token with DNS-edit permissions. PIC uses it to complete the DNS-01 challenge and update A records when your IP changes.

duckdns

You provide a DuckDNS token and subdomain. PIC completes the DNS-01 challenge through the DuckDNS API.

http01

Let's Encrypt issues individual certificates for each subdomain by making an HTTP request to your cell on port 80. There is no wildcard — each new service subdomain requires its own challenge. Port 80 must be publicly reachable.

lan

PIC's internal certificate authority (managed by VaultManager) issues TLS certificates. No internet access is required. Clients that need to trust your cell's HTTPS need to import the internal CA certificate, which is available from the Vault page in the admin dashboard.


How TLS works in practice

Caddy handles all TLS termination. When a new service is installed, PIC regenerates the Caddyfile and either reloads Caddy hot (for most changes) or requests a new certificate from Let's Encrypt. Internal services get a cert from the internal CA.

Certificate status (expiry date, days remaining) is visible from the Settings page under Vault & Trust, or via:

GET /api/caddy/cert-status

You can force a renewal attempt:

POST /api/caddy/cert-renew

Split-horizon DNS

PIC's CoreDNS (cell-dns) serves a split-horizon configuration for your cell domain:

  • Inside the VPN: the cell domain resolves to the WireGuard IP of cell-caddy. Traffic flows through the tunnel.
  • Outside the VPN: the DDNS provider's A record points to your public IP.

This means the same URL works on and off the VPN. The DNS answer changes depending on which network the client is on.

The .cell TLD is served authoritatively by CoreDNS and resolves only for VPN-connected clients.


DDNS heartbeat

For pic_ngo, cloudflare, and duckdns modes, a background thread in the API runs every 5 minutes. It checks your current public IP and calls the provider's update API only when the IP has changed.

DDNS configuration lives in config/api/cell_config.json under the ddns key. The bearer token (for pic_ngo) is stored in data/api/.ddns_token.


Host NTP

The installer automatically installs and enables chrony on the host. This is required for two things:

  1. ACME certificate issuance — Let's Encrypt rejects challenges with a skewed clock.
  2. DDNS registration — the pic.ngo registration endpoint requires a valid TOTP, which depends on the clock.

If you installed manually without installing chrony, do it now:

# Debian / Ubuntu
sudo apt-get install -y chrony && sudo systemctl enable --now chrony

# Fedora / RHEL
sudo dnf install -y chrony && sudo systemctl enable --now chronyd

# Alpine
sudo apk add chrony && sudo rc-update add chronyd default && sudo service chronyd start

The cell-ntp container (chrony) serves NTP to VPN-connected peers separately from the host chrony. These are two different chrony processes serving different purposes.


Changing the domain mode

You can change domain modes from the Settings page in the admin dashboard. Changing the mode:

  1. Updates the identity config
  2. Regenerates the Caddyfile
  3. Re-registers or deregisters the DDNS subdomain as appropriate
  4. Rebuilds CoreDNS split-horizon zones

ℹ️ Note: If you change from pic_ngo to lan, the DDNS registration is not automatically removed. You will need to do that manually if needed.

Internals: see Dev – Architecture