Load Balancers

A load balancer is a dedicated server that becomes the public entry point for your app. It terminates HTTPS, holds your domains and certificates, and forwards traffic over your private network to the app server, which keeps running your app exactly as before. Your app server stops being the address the internet talks to.

Today a load balancer fronts a single app server. It's the foundation for running one app on multiple servers, so putting an app behind a load balancer now means adding more servers later is an extension, not a migration.

Load balancers are available on plans with clusters. See Plans & Pricing.

Creating a Load Balancer

Click Add server in the server bar at the top of the dashboard, the same form you use for an app server. Set the Type field to Load Balancer (Caddy) and fill in the rest:

  • Name - a label for your reference, like lb-prod-01.
  • Provider - DigitalOcean or Hetzner Cloud. Create the load balancer on the same provider as the app server it will front.
  • Region - a load balancer and the app server behind it must share one private network. On DigitalOcean, they have to be in the same region. On Hetzner, they have to be in the same network zone - the European locations (Nuremberg, Falkenstein, Helsinki) share one zone, while Ashburn, Hillsboro, and Singapore are each their own. See Server Regions & Sizes.
  • Size - a load balancer only runs Caddy, so the smallest sizes handle substantial traffic. You can start small and resize later.

Provisioning takes a few minutes. Potions installs Caddy with the same certificate capabilities as an app server, so wildcard and on-demand certificates work at the load balancer, and locks the firewall to SSH and web traffic only. No PostgreSQL and no app tooling are installed: nothing deploys to a load balancer.

A load balancer counts as one of your servers against your plan's limit, the same as an app server.

Putting an App Behind the Load Balancer

Open the app's Settings tab and find the Load Balancer card. It lists the load balancers on the app server's private network. Pick one and click Enable load balancer.

Two things need to be true first: the app must have been deployed at least once, and the load balancer must be on the same private network as the app's server (same provider and region, as above).

Potions then does the setup automatically. It writes the proxy configuration on both servers, sends a test request through the load balancer to confirm the whole path works, and repoints your app's potions subdomain at the load balancer. If the app has no custom domains, that's the entire process - the status reads Active and traffic flows through the load balancer.

If setup fails, your app is unaffected: it keeps serving directly from its server, the card shows what went wrong, and a Retry setup button runs the process again.

Moving Custom Domains

Custom domains point at an IP address you control at your DNS provider, so Potions can't move them for you - instead it guides you through a cutover with no downtime.

While domains are waiting to move, the app's status reads Waiting on domain DNS and the Domains tab shows a banner with the load balancer's IP address. Each domain that still needs to move shows a DNS update needed badge. For each one:

  1. At your DNS provider, update the domain's A record to the load balancer's IP.
  2. Click Verify DNS on the domain.

Until you switch a domain, it keeps being served from the old address - both servers serve during the cutover, so there's no moment where a domain stops working. When the last domain verifies, the status flips to Active.

New domains added while the load balancer is active just follow the normal flow: the DNS instructions already show the load balancer's IP.

Deploys and Day-to-Day Operation

Nothing about deploys changes. Pushes and manual deploys build and roll out with zero downtime exactly as before - the blue-green switch happens on the app server, and the load balancer isn't touched or reloaded by a deploy.

Backups, logs, the console, scheduled tasks, and database moves all work unchanged. During a database move, visitors see the maintenance page through the load balancer, the same as they would without one.

Your app keeps seeing real visitor information. Potions configures the app server to trust the load balancer, so the X-Forwarded-For header carries the visitor's IP address and X-Forwarded-Proto stays https. If your app reads client IPs, take them from X-Forwarded-For - the remote_ip library handles this well.

Wildcard and Multi-Tenant Apps

Multi-tenant apps work behind a load balancer, with a few specifics:

  • Your wildcard domain moves in the same guided cutover as any custom domain: point the wildcard A record (*.yourdomain.com) at the load balancer and verify it. Wildcard certificates keep issuing without interruption.
  • Tenant custom domains aren't managed in Potions, so they follow however your tenants' DNS is set up. Tenants using a CNAME to your domain move automatically when you move it. Tenants with a direct A record to the old IP need to update it.
  • The first visit to each tenant domain after the cutover takes a few extra seconds while the load balancer issues its certificate.
  • A load balancer can front many apps, but only one app with tenant custom domains - the certificate approval endpoint is global to the load balancer, the same way it's one per server today.

Disabling

Click Disable load balancer on the app's Settings tab to return the app to serving directly from its server. It's the same guided process in reverse: the potions subdomain moves back automatically, and the Domains tab walks you through pointing each custom domain back at the app server. The load balancer keeps serving every domain until you've switched it, so there's no downtime here either. Once the last domain verifies, the load balancer steps out of the path entirely.

Things to Know

  • The load balancer is the single entry point for the apps behind it. If the load balancer server goes down, those apps are unreachable until it recovers, even though the apps themselves are still running. It gets monitoring and alerts like any other server.
  • One load balancer can front multiple apps. Each app gets its own configuration on the load balancer, and enabling or disabling one app never interrupts the others.
  • A load balancer can't be deleted while apps are behind it. Disable the load balancer on those apps first.
  • Certificates move with the traffic. While an app is behind a load balancer, its certificates are issued and renewed there. When you disable it, issuance returns to the app server.
  • Configuration changes briefly reconnect live WebSockets. When the load balancer's configuration changes - a custom domain added or removed, a cluster node attached or detached - open WebSocket connections (LiveView sessions, for example) are closed and reconnect automatically, typically within a second or two. Day-to-day traffic and deploys never touch the load balancer's configuration, so this only happens on those explicit changes.