Server Provisioning#

PosternProxy automates the full setup of a remote server via SSH. This page documents what the provisioner does and how to troubleshoot it.

Prerequisites#

The remote server must have:

  • Debian 11/12 or Ubuntu 22.04/24.04
  • Root access (or a user with passwordless sudo)
  • Port 22 open (SSH)
  • Outbound internet access (to download packages and Let’s Encrypt)
  • Outbound access to the controller on port 81 (for the agent WebSocket)

What the provisioner installs#

Caddy#

The provisioner builds Caddy from source using xcaddy, including the required plugins:

xcaddy build \
  --with github.com/mholt/caddy-l4 \
  --with github.com/mholt/caddy-ratelimit \
  --output /usr/bin/caddy

The agent binary#

The posternproxy-agent binary is compiled by the controller’s build script and embedded in the controller binary. During provisioning it is SCP’d to /usr/local/bin/posternproxy-agent on the remote server.

System users#

Two dedicated system users with no login shell:

  • caddy — runs the Caddy process
  • posternproxy-agent — runs the agent process

Systemd services#

Both Caddy and the agent run as hardened systemd services:

[Service]
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
NoNewPrivileges=true
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
SystemCallFilter=@system-service
MemoryDenyWriteExecute=true

Security hardening#

ComponentAction
UFWAllow 80, 443, SSH port; deny all else
fail2banInstall + configure with a Caddy/agent login jail
unattended-upgradesInstall + enable for security updates
sysctlrp_filter=1, tcp_syncookies=1, disable ICMP redirects
IP forwardingEnable net.ipv4.ip_forward=1 for port forwarding support

Agent config#

Written to /etc/posternproxy-agent/config.env:

POSTERNPROXY_CONTROLLER_URL=http://10.0.0.1:81
POSTERNPROXY_AGENT_TOKEN=<unique-per-server-token>

The token is generated by the controller, stored bcrypt-hashed in the servers table, and shown to the admin only at registration time.

Real-time provisioning log#

The provisioning wizard streams live output via Server-Sent Events (SSE) to the terminal panel in the UI. Each line of the SSH session output is forwarded to the browser as it arrives.

The log is also stored in the servers.provision_log column in the database, accessible after the fact via:

curl -H "Authorization: Bearer <token>" \
     http://your-controller:81/api/servers/{id}/provision-log

Troubleshooting#

Agent does not come online after provisioning#

  1. SSH into the remote server and check the agent service:
systemctl status posternproxy-agent
journalctl -u posternproxy-agent -n 50
  1. Verify the controller URL is reachable from the server:
curl http://<controller-ip>:81/api/auth/me
  1. Check UFW is not blocking outbound connections:
ufw status

Provisioning fails mid-way#

Click Re-Provision in the Servers page to restart the provisioning script from the beginning. The script is idempotent — it can safely be re-run on a partially provisioned server.

SSH authentication failure#

  • Verify the SSH credentials are correct by connecting manually
  • If using a private key, ensure it is in OpenSSH PEM format (not PKCS8)
  • Check that the SSH user has root or passwordless sudo access

Manual agent installation#

If automated provisioning is not suitable (e.g. the server is behind a jump host), you can install the agent manually:

  1. Build the agent binary: bash scripts/build.sh
  2. Copy posternproxy-agent to /usr/local/bin/ on the remote server
  3. Create /etc/posternproxy-agent/config.env with the controller URL and agent token (shown in the Servers page after registering the server without provisioning)
  4. Install the systemd service:
cat > /etc/systemd/system/posternproxy-agent.service << EOF
[Unit]
Description=PosternProxy Agent
After=network.target

[Service]
Type=simple
User=posternproxy-agent
EnvironmentFile=/etc/posternproxy-agent/config.env
ExecStart=/usr/local/bin/posternproxy-agent
Restart=always
RestartSec=5
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now posternproxy-agent