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:
- ACME certificate issuance — Let's Encrypt rejects challenges with a skewed clock.
- DDNS registration — the
pic.ngoregistration 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:
- Updates the identity config
- Regenerates the Caddyfile
- Re-registers or deregisters the DDNS subdomain as appropriate
- Rebuilds CoreDNS split-horizon zones
ℹ️ Note: If you change from
pic_ngotolan, the DDNS registration is not automatically removed. You will need to do that manually if needed.
Internals: see Dev – Architecture
Personal Internet Cell
New here?
Users
User – Connect to the VPN User – Use Your Services User – Troubleshooting
Admins
Admin – Overview Admin – Install and First Run Admin – Configure Domains and TLS Admin – Manage Services Admin – Configure Connectivity Admin – Manage Peers Admin – Back Up and Restore Admin – Logging and Audit Admin – Monitor and Troubleshoot
Developers
Dev – Overview Dev – Architecture Dev – Build a Store Service Dev – Service Manifest Reference Dev – API Reference Dev – Testing Dev – Install Internals
Decisions (ADRs)
ADR – 001 Store Images Are Signed and Verified by Cells ADR – 002 Named Connection Instances for Connectivity ADR – 003 All Optional Functionality Ships as Store Services
Meta
Meta – Glossary Meta – Template Runbook Meta – Template ADR
Archive
Archive – User Guide Archive – ADR 004 The Wiki Is the Single Documentation Source