<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>PosternProxy Documentation</title><link>https://d8f649ee.posternproxydocs.pages.dev/</link><description>Recent content on PosternProxy Documentation</description><generator>Hugo</generator><language>en-US</language><atom:link href="https://d8f649ee.posternproxydocs.pages.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>Installation</title><link>https://d8f649ee.posternproxydocs.pages.dev/getting-started/installation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/getting-started/installation/</guid><description>&lt;h1 id="installation"&gt;Installation&lt;a class="anchor" href="#installation"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;PosternProxy installs directly on Debian 11/12 or Ubuntu 22.04/24.04. No Docker required.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;a class="anchor" href="#prerequisites"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A fresh Debian or Ubuntu server with root access&lt;/li&gt;
&lt;li&gt;Ports 80, 443, and 81 open in your firewall&lt;/li&gt;
&lt;li&gt;A domain or IP for the management UI&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="automated-install"&gt;Automated install&lt;a class="anchor" href="#automated-install"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The install script handles everything: building Caddy with required plugins, creating system users, configuring systemd, UFW, fail2ban, and unattended-upgrades.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1 — Build the binary&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;On your development machine (or CI), run:&lt;/p&gt;</description></item><item><title>Load Balancing</title><link>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/load-balancing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/load-balancing/</guid><description>&lt;h1 id="load-balancing"&gt;Load Balancing&lt;a class="anchor" href="#load-balancing"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;PosternProxy supports distributing traffic across multiple upstream backends for a single proxy host. Configuration is on the &lt;strong&gt;Upstreams&lt;/strong&gt; tab of the Add/Edit Proxy Host modal.&lt;/p&gt;
&lt;h2 id="adding-backends"&gt;Adding backends&lt;a class="anchor" href="#adding-backends"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first upstream is always the &lt;strong&gt;Primary&lt;/strong&gt; backend (set on the &lt;strong&gt;Details&lt;/strong&gt; tab). Additional backends are added on the &lt;strong&gt;Upstreams&lt;/strong&gt; tab. Each extra backend requires:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Host / IP&lt;/strong&gt; — the internal address of the backend server&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Port&lt;/strong&gt; — the port the backend listens on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is no enforced limit on the number of backends.&lt;/p&gt;</description></item><item><title>Provisioning</title><link>https://d8f649ee.posternproxydocs.pages.dev/multi-server/provisioning/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/multi-server/provisioning/</guid><description>&lt;h1 id="server-provisioning"&gt;Server Provisioning&lt;a class="anchor" href="#server-provisioning"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;PosternProxy automates the full setup of a remote server via SSH. This page documents what the provisioner does and how to troubleshoot it.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;a class="anchor" href="#prerequisites"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The remote server must have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Debian 11/12 or Ubuntu 22.04/24.04&lt;/li&gt;
&lt;li&gt;Root access (or a user with passwordless sudo)&lt;/li&gt;
&lt;li&gt;Port 22 open (SSH)&lt;/li&gt;
&lt;li&gt;Outbound internet access (to download packages and Let&amp;rsquo;s Encrypt)&lt;/li&gt;
&lt;li&gt;Outbound access to the controller on port 81 (for the agent WebSocket)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-the-provisioner-installs"&gt;What the provisioner installs&lt;a class="anchor" href="#what-the-provisioner-installs"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="caddy"&gt;Caddy&lt;a class="anchor" href="#caddy"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The provisioner builds Caddy from source using &lt;code&gt;xcaddy&lt;/code&gt;, including the required plugins:&lt;/p&gt;</description></item><item><title>Security Hardening</title><link>https://d8f649ee.posternproxydocs.pages.dev/administration/security-hardening/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/administration/security-hardening/</guid><description>&lt;h1 id="security-hardening"&gt;Security Hardening&lt;a class="anchor" href="#security-hardening"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The PosternProxy install script applies a comprehensive set of security hardening measures. This page documents what is applied and how to verify it.&lt;/p&gt;
&lt;h2 id="application-level-hardening"&gt;Application-level hardening&lt;a class="anchor" href="#application-level-hardening"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="authentication"&gt;Authentication&lt;a class="anchor" href="#authentication"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JWT access tokens with 15-minute TTL&lt;/li&gt;
&lt;li&gt;Refresh tokens with 7-day TTL stored in HttpOnly / Secure / SameSite=Strict cookies&lt;/li&gt;
&lt;li&gt;bcrypt password hashing at cost factor 12&lt;/li&gt;
&lt;li&gt;Login rate limit: 5 attempts/min per IP (enforced by the API and by fail2ban)&lt;/li&gt;
&lt;li&gt;Account lockout is not time-based — use fail2ban to block IPs&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="api-security"&gt;API security&lt;a class="anchor" href="#api-security"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rate limiting:&lt;/strong&gt; 120 requests/min per IP on authenticated management API routes; 5 login attempts/min per IP; 30 agent WebSocket connection attempts/min per IP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSRF protection:&lt;/strong&gt; Double-submit cookie pattern on all state-changing requests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure headers on all responses:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;X-Content-Type-Options: nosniff&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-Frame-Options: DENY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Strict-Transport-Security: max-age=31536000; includeSubDomains&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Security-Policy: default-src 'self'&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Input validation:&lt;/strong&gt; All user input validated at the handler layer; domain names validated per RFC 1123; ports validated 1–65535; CIDRs parsed and validated; all database queries use parameterized statements (no SQL interpolation)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="encrypted-storage"&gt;Encrypted storage&lt;a class="anchor" href="#encrypted-storage"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;DNS provider credentials and SSH credentials are encrypted at rest using AES-256-GCM with a key derived from the JWT secret.&lt;/p&gt;</description></item><item><title>Health Checks</title><link>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/health-checks/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/health-checks/</guid><description>&lt;h1 id="health-checks"&gt;Health Checks&lt;a class="anchor" href="#health-checks"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;PosternProxy exposes Caddy&amp;rsquo;s built-in health-check machinery through the &lt;strong&gt;Health&lt;/strong&gt; tab of the proxy host form. Health checks keep your load balancer from routing traffic to backends that are down or degraded.&lt;/p&gt;
&lt;h2 id="active-health-checks"&gt;Active health checks&lt;a class="anchor" href="#active-health-checks"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Active checks probe each upstream on a schedule by sending an HTTP request and evaluating the response.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Setting&lt;/th&gt;
					&lt;th&gt;Default&lt;/th&gt;
					&lt;th&gt;Description&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Path&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;URL path Caddy requests on each backend&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Interval&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;30s&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;How often to run the check&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Timeout&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;5s&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Maximum time to wait for a response&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Expected status&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;200&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;HTTP status code that counts as healthy&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Enable active checks with the &lt;strong&gt;Enable health checks&lt;/strong&gt; toggle on the Health tab.&lt;/p&gt;</description></item><item><title>Quick Start</title><link>https://d8f649ee.posternproxydocs.pages.dev/getting-started/quick-start/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/getting-started/quick-start/</guid><description>&lt;h1 id="quick-start"&gt;Quick Start&lt;a class="anchor" href="#quick-start"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This guide walks you through the most common first-day tasks after installation.&lt;/p&gt;
&lt;h2 id="1-log-in-and-change-the-default-password"&gt;1. Log in and change the default password&lt;a class="anchor" href="#1-log-in-and-change-the-default-password"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Open &lt;code&gt;http://&amp;lt;server-ip&amp;gt;:81&lt;/code&gt; in your browser.&lt;/p&gt;
&lt;!-- TODO: Screenshot — login page --&gt;
&lt;p&gt;On first boot, PosternProxy generates a random admin password and prints it to the service logs. Retrieve it before logging in:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;journalctl -u posternproxy --no-pager | grep -A5 &lt;span style="color:#a5d6ff"&gt;&amp;#34;INITIAL SETUP&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Log in with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Email:&lt;/strong&gt; &lt;code&gt;admin@posternproxy.local&lt;/code&gt; (or the value of &lt;code&gt;POSTERNPROXY_ADMIN_EMAIL&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password:&lt;/strong&gt; the generated password from the startup banner above&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Immediately navigate to &lt;strong&gt;Users → (your account) → Change Password&lt;/strong&gt; and set a strong password.&lt;/p&gt;</description></item><item><title>Upgrading</title><link>https://d8f649ee.posternproxydocs.pages.dev/administration/upgrading/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/administration/upgrading/</guid><description>&lt;h1 id="upgrading-posternproxy"&gt;Upgrading PosternProxy&lt;a class="anchor" href="#upgrading-posternproxy"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="upgrading-the-controller"&gt;Upgrading the controller&lt;a class="anchor" href="#upgrading-the-controller"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Build the new binary&lt;/strong&gt; on your development machine:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bash scripts/build.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Copy the binary to the server:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;scp posternproxy user@your-server:/tmp/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stop the service, replace the binary, restart:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh root@your-server
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl stop posternproxy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp /tmp/posternproxy /usr/local/bin/posternproxy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl start posternproxy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Verify the service started:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl status posternproxy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;journalctl -u posternproxy -n &lt;span style="color:#a5d6ff"&gt;20&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="database-migrations"&gt;Database migrations&lt;a class="anchor" href="#database-migrations"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;PosternProxy runs embedded migrations automatically on startup. If a new version adds database columns or tables, the migration runs before the service starts serving requests. No manual intervention is needed.&lt;/p&gt;</description></item><item><title>SSL / TLS</title><link>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/ssl-tls/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/ssl-tls/</guid><description>&lt;h1 id="ssl--tls"&gt;SSL / TLS&lt;a class="anchor" href="#ssl--tls"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;PosternProxy leverages Caddy&amp;rsquo;s automatic HTTPS to handle TLS with minimal configuration. The &lt;strong&gt;SSL&lt;/strong&gt; tab controls certificate selection and security options.&lt;/p&gt;
&lt;h2 id="automatic-lets-encrypt-default"&gt;Automatic Let&amp;rsquo;s Encrypt (default)&lt;a class="anchor" href="#automatic-lets-encrypt-default"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Leave the &lt;strong&gt;SSL Certificate&lt;/strong&gt; field set to &lt;strong&gt;None / Caddy automatic&lt;/strong&gt;. Caddy will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Obtain a certificate from Let&amp;rsquo;s Encrypt the first time a request arrives for the domain&lt;/li&gt;
&lt;li&gt;Store the certificate in its managed certificate store&lt;/li&gt;
&lt;li&gt;Renew automatically before expiry&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Requirements for automatic certificates:&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Troubleshooting</title><link>https://d8f649ee.posternproxydocs.pages.dev/administration/troubleshooting/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/administration/troubleshooting/</guid><description>&lt;h1 id="troubleshooting"&gt;Troubleshooting&lt;a class="anchor" href="#troubleshooting"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="service-not-starting"&gt;Service not starting&lt;a class="anchor" href="#service-not-starting"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="posternproxy-fails-to-start"&gt;PosternProxy fails to start&lt;a class="anchor" href="#posternproxy-fails-to-start"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;journalctl -u posternproxy -n &lt;span style="color:#a5d6ff"&gt;50&lt;/span&gt; --no-pager&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Common causes:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Symptom&lt;/th&gt;
					&lt;th&gt;Cause&lt;/th&gt;
					&lt;th&gt;Fix&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;database is locked&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Another PosternProxy instance is running&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;systemctl stop posternproxy; systemctl start posternproxy&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;migration failed&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Bad database state&lt;/td&gt;
					&lt;td&gt;Restore from backup&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;address already in use :81&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Another process owns port 81&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ss -tlnp | grep 81&lt;/code&gt; to find it&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;config.env not found&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Missing config file&lt;/td&gt;
					&lt;td&gt;Re-run &lt;code&gt;scripts/install.sh&lt;/code&gt; or create manually&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="caddy-fails-to-start"&gt;Caddy fails to start&lt;a class="anchor" href="#caddy-fails-to-start"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;journalctl -u caddy -n &lt;span style="color:#a5d6ff"&gt;50&lt;/span&gt; --no-pager
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;caddy validate --config /etc/caddy/Caddyfile &lt;span style="color:#8b949e;font-style:italic"&gt;# if using Caddyfile fallback&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Common causes:&lt;/p&gt;</description></item><item><title>Rate Limiting</title><link>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/rate-limiting/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/rate-limiting/</guid><description>&lt;h1 id="rate-limiting"&gt;Rate Limiting&lt;a class="anchor" href="#rate-limiting"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;PosternProxy can cap the number of requests a single IP address can make to a proxy host within a sliding time window. Rate limiting is configured on the &lt;strong&gt;Security&lt;/strong&gt; tab.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;a class="anchor" href="#how-it-works"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rate limiting is implemented via the &lt;a href="https://github.com/mholt/caddy-ratelimit"&gt;caddy-ratelimit&lt;/a&gt; Caddy plugin, which is included in the PosternProxy Caddy build.&lt;/p&gt;
&lt;p&gt;When a client exceeds the limit, Caddy returns a &lt;code&gt;429 Too Many Requests&lt;/code&gt; response. The limit resets on a sliding window basis — it is not a hard per-minute bucket.&lt;/p&gt;</description></item><item><title>TLS Passthrough</title><link>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/tls-passthrough/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/tls-passthrough/</guid><description>&lt;h1 id="tls-passthrough"&gt;TLS Passthrough&lt;a class="anchor" href="#tls-passthrough"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;TLS Passthrough lets Caddy forward encrypted TLS connections directly to an upstream without terminating (decrypting) them. The upstream handles TLS itself — Caddy never sees the plaintext.&lt;/p&gt;
&lt;h2 id="when-to-use-it"&gt;When to use it&lt;a class="anchor" href="#when-to-use-it"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Use TLS Passthrough when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The upstream requires &lt;strong&gt;mutual TLS (mTLS)&lt;/strong&gt; / client certificates that Caddy cannot forward&lt;/li&gt;
&lt;li&gt;The upstream is a &lt;strong&gt;database&lt;/strong&gt; or other service presenting its own TLS certificate&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;end-to-end encryption&lt;/strong&gt; between client and upstream with no proxy in the middle&lt;/li&gt;
&lt;li&gt;The upstream uses a &lt;strong&gt;private CA&lt;/strong&gt; certificate that Caddy cannot validate&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-to-enable"&gt;How to enable&lt;a class="anchor" href="#how-to-enable"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On the &lt;strong&gt;Details&lt;/strong&gt; tab, toggle &lt;strong&gt;TLS Passthrough&lt;/strong&gt; on. When enabled:&lt;/p&gt;</description></item><item><title>Custom Error Pages</title><link>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/custom-error-pages/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/custom-error-pages/</guid><description>&lt;h1 id="custom-error-pages"&gt;Custom Error Pages&lt;a class="anchor" href="#custom-error-pages"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;PosternProxy can serve custom HTML pages when a proxy host returns a 404, 502, or 503 status code. This replaces Caddy&amp;rsquo;s default plain-text error responses with your own branded or user-friendly HTML.&lt;/p&gt;
&lt;h2 id="configuration"&gt;Configuration&lt;a class="anchor" href="#configuration"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Open the &lt;strong&gt;Errors&lt;/strong&gt; tab of the Add/Edit Proxy Host modal. You will see three panels, one for each supported status code:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Code&lt;/th&gt;
					&lt;th&gt;Meaning&lt;/th&gt;
					&lt;th&gt;Common cause&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;404&lt;/td&gt;
					&lt;td&gt;Not Found&lt;/td&gt;
					&lt;td&gt;Route exists but upstream returned 404; or path not matched&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;502&lt;/td&gt;
					&lt;td&gt;Bad Gateway&lt;/td&gt;
					&lt;td&gt;Upstream is down or returned an invalid response&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;503&lt;/td&gt;
					&lt;td&gt;Service Unavailable&lt;/td&gt;
					&lt;td&gt;Upstream is overloaded or in maintenance mode&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Paste your HTML into the relevant textarea. Leave a field blank to use Caddy&amp;rsquo;s default behaviour for that code.&lt;/p&gt;</description></item><item><title>Custom Headers</title><link>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/custom-headers/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/custom-headers/</guid><description>&lt;h1 id="custom-headers"&gt;Custom Headers&lt;a class="anchor" href="#custom-headers"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;PosternProxy can inject, modify, or remove HTTP headers on requests sent to your upstream and on responses returned to clients. Header rules are configured on the &lt;strong&gt;Headers&lt;/strong&gt; tab.&lt;/p&gt;
&lt;h2 id="adding-a-rule"&gt;Adding a rule&lt;a class="anchor" href="#adding-a-rule"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Each rule has four fields:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Field&lt;/th&gt;
					&lt;th&gt;Options&lt;/th&gt;
					&lt;th&gt;Description&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Direction&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Request / Response&lt;/td&gt;
					&lt;td&gt;Whether the rule applies to the upstream request or the downstream response&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Operation&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Set / Add / Delete&lt;/td&gt;
					&lt;td&gt;What to do with the header&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Name&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Any string&lt;/td&gt;
					&lt;td&gt;The header name (case-insensitive)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Value&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Any string&lt;/td&gt;
					&lt;td&gt;The header value (not used for Delete)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Click &lt;strong&gt;+ Add Rule&lt;/strong&gt; to insert a new row. Rows with an empty Name are ignored on save.&lt;/p&gt;</description></item><item><title>Custom Locations</title><link>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/locations/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/locations/</guid><description>&lt;h1 id="custom-locations"&gt;Custom Locations&lt;a class="anchor" href="#custom-locations"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Custom locations let you override proxy behaviour for specific URL paths within a proxy host. They are configured on the &lt;strong&gt;Locations&lt;/strong&gt; tab.&lt;/p&gt;
&lt;h2 id="what-locations-do"&gt;What locations do&lt;a class="anchor" href="#what-locations-do"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default, a proxy host forwards all paths to the same upstream. Locations allow you to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Send &lt;code&gt;/api/*&lt;/code&gt; to a different backend than &lt;code&gt;/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Apply different &lt;code&gt;forward_scheme&lt;/code&gt; per path&lt;/li&gt;
&lt;li&gt;Set path-specific Caddy directives using &lt;strong&gt;Advanced Config&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="adding-a-location"&gt;Adding a location&lt;a class="anchor" href="#adding-a-location"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Click &lt;strong&gt;+ Add Location&lt;/strong&gt; on the Locations tab. Each location has:&lt;/p&gt;</description></item><item><title>Config History</title><link>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/config-history/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://d8f649ee.posternproxydocs.pages.dev/proxy-hosts/config-history/</guid><description>&lt;h1 id="config-history"&gt;Config History&lt;a class="anchor" href="#config-history"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;PosternProxy records a snapshot of every proxy host&amp;rsquo;s configuration before each change. You can browse the history and roll back to any previous state.&lt;/p&gt;
&lt;h2 id="viewing-history"&gt;Viewing history&lt;a class="anchor" href="#viewing-history"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Open the &lt;strong&gt;⋮&lt;/strong&gt; menu on any proxy host row in the table and click &lt;strong&gt;History&lt;/strong&gt;. A drawer slides open showing the last 100 snapshots in reverse chronological order.&lt;/p&gt;
&lt;p&gt;Each entry shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Timestamp of the change&lt;/li&gt;
&lt;li&gt;The user who made it&lt;/li&gt;
&lt;li&gt;A brief diff of what changed&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- TODO: Screenshot — Config History drawer with several entries --&gt;
&lt;h2 id="rolling-back"&gt;Rolling back&lt;a class="anchor" href="#rolling-back"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Click &lt;strong&gt;Restore&lt;/strong&gt; on any snapshot to revert the proxy host to that state. PosternProxy will:&lt;/p&gt;</description></item></channel></rss>