Back to Overview

Reverse Proxy Setup

Run Tymeslot behind Nginx or Caddy with automatic HTTPS. Required for production deployments and OAuth integrations.

Luka Breitig โ€” Technical Product Builder & AI Developer
Luka Breitig

Technical Product Builder & AI Developer

๐Ÿ“‹ Before You Begin

Software

  • Caddy 2.x (recommended) โ€” or โ€”
  • Nginx 1.18+ with Certbot
  • Tymeslot running and reachable at localhost:4000

Requirements

  • Root or sudo access on the server
  • A domain name with its DNS A record pointing to this server's IP address
  • Ports 80 and 443 open in your firewall

Outcome: By the end of this guide, Tymeslot will be accessible over HTTPS at your domain, with WebSocket support enabled so all real-time features work correctly.

๐Ÿ”€ Why a Reverse Proxy?

Tymeslot listens on port 4000 without SSL. A reverse proxy sits in front of it and handles three critical concerns:

  • HTTPS termination โ€” automatic Let's Encrypt certificates via Caddy or Certbot. Without HTTPS, browsers will refuse to connect.
  • WebSocket proxying โ€” Phoenix LiveView uses a persistent WebSocket connection to push real-time updates to the browser. Without it, the page loads but all live updates stop working.
  • OAuth compatibility โ€” Google, Microsoft, and GitHub all require HTTPS callback URLs. OAuth integrations will not work without it.

1 Option A โ€” Caddy (Recommended)

Caddy provisions and renews Let's Encrypt certificates with zero configuration. No cron jobs, no manual renewal commands.

Install Caddy

Run the following on your server (Ubuntu/Debian):

apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  | tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install caddy

You should see Setting up caddy in the output and the Caddy service will be enabled automatically.

Create the Caddyfile

Open /etc/caddy/Caddyfile in your editor and replace its contents with:

tymeslot.yourdomain.com {
    reverse_proxy localhost:4000
    encode gzip
}

Reload Caddy to apply the configuration:

sudo systemctl reload caddy

Within 30 seconds, Caddy will contact Let's Encrypt, obtain a certificate, and begin serving your domain over HTTPS. The first request to your domain may take a moment while the certificate is provisioned.

Port 80 Must Be Free

Caddy uses port 80 for the ACME HTTP-01 challenge to prove domain ownership before issuing a certificate. If another process (such as Apache or an old Nginx install) is holding port 80, Caddy will fail silently. Run ss -tlnp | grep :80 to check.

2 Option B โ€” Nginx + Certbot (Alternative)

Use this if Nginx is already installed on your server or required by your infrastructure team.

Install Nginx and Certbot

apt install nginx certbot python3-certbot-nginx

You should see both nginx and certbot listed as newly installed packages.

Create the site configuration

Create the file /etc/nginx/sites-available/tymeslot with the following contents. The Upgrade and Connection headers in the location block are essential โ€” they tell Nginx to forward WebSocket upgrade requests to Tymeslot rather than dropping them:

server {
    listen 80;
    server_name tymeslot.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name tymeslot.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/tymeslot.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/tymeslot.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://localhost:4000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 86400;
    }
}

Enable the site and obtain a certificate

# Symlink the config into sites-enabled
ln -s /etc/nginx/sites-available/tymeslot /etc/nginx/sites-enabled/

# Obtain a Let's Encrypt certificate (Certbot edits the config automatically)
sudo certbot --nginx -d tymeslot.yourdomain.com

# Test the config and reload
nginx -t && systemctl reload nginx

After certbot completes, you should see Congratulations! and the path to your new certificate. The nginx -t command should return syntax is ok and test is successful.

WebSocket Headers Are Not Optional

Phoenix LiveView maintains a persistent WebSocket connection between the browser and the server. If the Upgrade and Connection "upgrade" headers are missing, the page will load but all real-time features โ€” availability updates, booking confirmation, search results โ€” will stop responding. This is the most common Nginx misconfiguration with Phoenix applications.

If you get a 502 Bad Gateway

This means Nginx cannot reach Tymeslot on port 4000. The application may not be running. Check with:

docker-compose logs tymeslot

Look for startup errors or a missing database connection in the output.

3 Update Environment Variables

Now that Tymeslot is behind HTTPS, update your .env file so the application generates correct URLs for emails, OAuth callbacks, and links:

URL_SCHEME=https
PHX_HOST=tymeslot.yourdomain.com

Restart Tymeslot after editing the file:

docker-compose down && docker-compose up -d

You should see the containers start cleanly. Check docker-compose logs tymeslot if the application does not come up within 15 seconds.

Why These Variables Matter

PHX_HOST is used to construct absolute URLs for calendar invites, email notifications, and OAuth redirect URIs. If it does not match the domain your reverse proxy serves, OAuth logins will fail and email links will point to the wrong host.

โšก WebSocket Support Explained

When a visitor opens Tymeslot in their browser, Phoenix LiveView immediately upgrades the HTTP connection to a WebSocket. This persistent channel is how the server pushes instant updates โ€” booking confirmations, availability changes, form validation โ€” without the browser needing to poll.

The upgrade happens via a special HTTP request with the header Upgrade: websocket. By default, most reverse proxies treat this as a normal HTTP request and strip the header. You must explicitly configure the proxy to pass it through.

If the page loads but stops updating in real time

The WebSocket upgrade headers are missing from your proxy configuration. The symptom is a page that appears to load correctly but never reflects changes โ€” search results do not update, booking forms do not respond, and you may see a connection error banner after 30โ€“60 seconds.

For Nginx, confirm these three lines are inside your location / block:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

โš–๏ธ Running Behind a Load Balancer or CDN

For AWS ALB, Cloudflare, or similar setups, the outermost proxy forwards headers that inner proxies must pass through. Tymeslot trusts X-Forwarded-Proto to determine the request scheme for URL generation.

If using Cloudflare, navigate to [SSL/TLS] in the Cloudflare dashboard and set the mode to Full (strict). Using Flexible causes Cloudflare to connect to your origin over HTTP while telling the origin it connected over HTTPS โ€” this produces redirect loops between Cloudflare and Caddy/Nginx.

Restricting WebSocket Origins

Set WS_ALLOWED_ORIGINS to a comma-separated list of allowed origins to restrict which domains can open a WebSocket connection to your instance. This is recommended when running behind a CDN. Defaults to allowing the configured host only.

โ“ Frequently Asked Questions

Phoenix LiveView disconnects repeatedly โ€” what's wrong with my proxy config?

LiveView relies on a persistent WebSocket connection. When a proxy drops or mishandles the WebSocket upgrade, the browser falls back to polling and then gives up, causing the repeated disconnect banner. For Nginx, confirm all three of these directives are present inside your location / block:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

For Caddy, the reverse_proxy directive handles WebSocket upgrades automatically โ€” no extra configuration is needed.

I get a '502 Bad Gateway' error โ€” what should I check?

A 502 means Nginx or Caddy reached the upstream address but received no response. The most common cause is that Tymeslot is not running or is still starting up. Check the container status and logs:

docker-compose ps
docker-compose logs tymeslot

Look for startup errors or a failed database connection in the output. Also confirm that the port in your proxy config matches the port Tymeslot is actually listening on โ€” the default is 4000.

Caddy handles HTTPS automatically โ€” do I still need to configure anything for SSL?

No additional SSL configuration is required. Caddy contacts Let's Encrypt automatically, obtains a certificate for the domain in your Caddyfile, and renews it before expiry โ€” all without any cron jobs or manual steps on your part.

The only prerequisites are that ports 80 and 443 are open in your firewall and that your domain's DNS A record points to the server's IP address. Without both, the ACME challenge will fail and no certificate will be issued. Run ss -tlnp | grep :80 to confirm nothing else is occupying port 80.

OAuth redirects fail after adding a reverse proxy โ€” why?

OAuth providers (Google, Microsoft, GitHub) compare the callback URL they receive against the one registered in their developer console. If Tymeslot generates an http:// callback URL when your proxy is serving https://, the comparison fails and the login is rejected.

Ensure two things are set in your .env file and that Tymeslot has been restarted after the change:

URL_SCHEME=https
PHX_HOST=tymeslot.yourdomain.com

Also verify that your Nginx config includes proxy_set_header X-Forwarded-Proto $scheme; so Tymeslot can detect that the incoming connection arrived over HTTPS.

Can I run Tymeslot behind Cloudflare?

Yes. Set Cloudflare's SSL/TLS mode to Full (strict) in the Cloudflare dashboard. Using Flexible causes Cloudflare to connect to your origin over plain HTTP while advertising HTTPS to the browser, which creates redirect loops with Caddy and Nginx.

Additionally, ensure WebSocket support is enabled for your Cloudflare zone โ€” navigate to Network in the Cloudflare dashboard and confirm WebSockets is toggled on. Without it, LiveView connections will be silently dropped by Cloudflare's edge before they reach your server.

Verify Your Setup

Confirm each of the following before considering the setup complete:
  • HTTPS loads without a certificate warning. Visit https://tymeslot.yourdomain.com in your browser. The address bar should show a padlock icon.
  • HTTP redirects to HTTPS. Visit http://tymeslot.yourdomain.com โ€” it should automatically redirect to the HTTPS version.
  • A booking page opens and loads fully. Navigate to any public booking link โ€” the page should render completely without a spinner stuck indefinitely.
  • Real-time updates work. Log in and navigate to any LiveView page (such as the dashboard). Type in a search field โ€” results should update immediately, confirming the WebSocket connection is live.
  • Health endpoint returns 200. Visit https://tymeslot.yourdomain.com/healthcheck in a browser โ€” it should return a plain 200 OK.

๐Ÿ”— Related Articles

Read Docker Self-Hosting

Docker Self-Hosting

Deploy Tymeslot using Docker and Docker Compose. Perfect for VPS hosting, home servers, or any environment with Docker support.

Read Cloudron Deployment

Cloudron Deployment

One-click installation on Cloudron. Automated backups, SSL certificates, and updates handled automatically.

Read Upgrading Tymeslot

Upgrading Tymeslot

Keep your Tymeslot instance up to date. Database migrations run automatically on startup โ€” upgrades are a single command.