Skip to content

Web server

Getting Clever with Caddy and Conduwuit / Matrix

A deep dive into We2.ee's Caddy configuration, handling Matrix federation, offering profile and room redirection shortlinks, and combining multiple services on a single domain.

My post earlier this evening discussed generally how Caddy has made self-hosting multiple web services a breeze. Here I want to build on that and look specifically at the Caddy configuration I use for We2.ee, the public Matrix homeserver I run and have posted about already.

The complete Caddyfile entry is available here. Let's look closer at a few key sections. First, you'll see a series of redirection handlers like this:

    handle /about {
        redir https://sij.law/we2ee/ permanent
    }

This one simply redirects we2.ee/about to sij.law/we2ee . This allows me to create authoritative "About" URL on the we2.ee domain but host the actual page here on my blog – saving me having to host a whole separate CMS just for We2.ee, and potentially lending credibility to We2.ee through my professional online presence here.

Next, you'll see some more redirection handlers that rely on regular expressions ("regex"):

    # Handle Matrix-style room redirects
    @matrix_local_room {
        path_regexp ^/@@([^:]+)$
    }
    redir @matrix_local_room https://matrix.to/#/%23{re.1}:we2.ee permanent

    # Handle Matrix-style room redirects with custom domains
    @matrix_remote_room {
        path_regexp ^/@@([^:]+):([^/]+)$
    }
    redir @matrix_remote_room https://matrix.to/#/%23{re.1}:{re.2} permanent

    # Handle Matrix-style user redirects
    @matrix_local_user {
        path_regexp ^/@([^:]+)$
    }
    redir @matrix_local_user https://matrix.to/#/@{re.1}:we2.ee permanent

    # Handle Matrix-style user redirects with custom domains
    @matrix_remote_user {
        path_regexp ^/@([^:]+):([^/]+)$
    }
    redir @matrix_remote_user https://matrix.to/#/@{re.1}:{re.2} permanent

These are particularly efficient—they allow for much shorter links for Matrix rooms and user profiles that redirect to the Matrix.to service. For example:

Next you'll see the handlers for the actual underlying services, Conduwuit and Element:

    # Handle Conduwuit homeserver
    handle /_matrix/* {
        reverse_proxy localhost:8448
    }

    # Handle federation
    handle /.well-known/matrix/server {
        header Access-Control-Allow-Origin "*"
        header Content-Type "application/json"
        respond `{"m.server": "we2.ee"}`
    }

    # Handle client discovery
    handle /.well-known/matrix/client {
        header Access-Control-Allow-Origin "*"
        header Content-Type "application/json"
        respond `{
            "m.homeserver": {"base_url": "https://we2.ee"},
            "org.matrix.msc3575.proxy": {"url": "https://we2.ee"}
        }`
    }

    # Handle MSC1929 Admin Contact Information
    handle /.well-known/matrix/support {
        header Access-Control-Allow-Origin "*"
        header Content-Type "application/json"
        respond `{
            "contacts": [
                {
                    "matrix_id": "@sij:we2.ee",
                    "email_address": "[email protected]",
                    "role": "m.role.admin"
                }
            ],
            "support_page": "https://we2.ee/about"
        }`
    }

    # Handle Element webUI
    handle {
        reverse_proxy localhost:8637
    }

This part of the Caddy configuration block:

  • serves up the actual Matrix homeserver (powered by conduwuit) at We2.ee
  • provides the necessary endpoints for federation
  • provides the MSC1929 endpoint for handling abuse reports, etc.
  • serves up an Element web interface for accessing the Matrix homeserver directly at We2.ee

I hope some of this is useful for folks running their own Matrix homeservers or anyone interested in seeing how Caddy configurations can be structured for more complex setups.

Simplifying Web Services with Caddy

Running multiple web services doesn't have to be complicated. Here's how Caddy makes it simple by handling reverse proxying and HTTPS certificates automatically, plus a script I use to set up new services with a single command.

After my recent posts about We2.ee, Lone.Earth, Earth.Law and that pump calculator project, several folks asked about managing multiple websites without it becoming a huge time sink. The secret isn't complicated—it's a neat tool called Caddy that handles most of the tedious parts automatically.

Understanding Reverse Proxies

Traditionally, web servers were designed with a simple model: one server running a single service on ports 80 (HTTP) and—more recently as infosec awareness increased—443 (HTTPS). This made sense when most organizations ran just one website or application per server. The web server would directly handle incoming requests and serve the content.

But this model doesn't work well for self-hosting. Most of us want to run multiple services on a single machine - maybe a blog like this, a chat service like We2.ee, and a few microservices like that pump calculator. We can't dedicate an entire server to each service—that would be wasteful and expensive—and we can't run them all on ports 80/443 (only one service can use a port at a time).

This is where reverse proxies come in. They act as a traffic director for your web server. Instead of services competing for ports 80 and 443, each service runs on its own port, and the reverse proxy directs traffic for

  • A blog to port 2368
  • A Mastodon instance to port 3000
  • An uptime tracking service to port 3001
  • A code hub to port 3003
  • An encrypted chat service to port 8448
  • DNS-over-HTTPS filter and resolver on 8502
  • A Peertube instance to port 9000
  • An LLM API to port 11434
  • ... etc.

When someone visits any of your websites, the reverse proxy looks at which domain they're trying to reach and routes them to the right service. That's really all there is to it—it's just routing traffic based on the requested domain.

Why Caddy Makes Life Easier

Caddy is a reverse proxy that manages this well and also happens to take care of one of the biggest headaches in web hosting: HTTPS certificates. Here's what my actual Caddy config looks like for this blog:

sij.law {
    reverse_proxy localhost:2368
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    }
}

This simple config tells Caddy to:

  • Send all traffic for sij.law to the blog running on port 2368
  • Automatically get HTTPS certificates from Let's Encrypt
  • Renew those certificates before they expire
  • Handle all the TLS/SSL security settings

If you've ever dealt with manual certificate management or complex web server configurations, you'll appreciate how much work these few lines are saving.

Making Domain Setup Even Easier

To streamline things further, I wrote a script that automates the whole domain setup process. When I was ready to launch that pump calculator I mentioned in my last post on the open web, I just ran:

cf pumpcalc.sij.ai --port 8901 --ip 100.64.64.11

One command and done—cf creates the DNS record on Cloudflare and points it to the IP of the server running Caddy, creates a Caddy configuration that reverse proxies pumpcalc.sij.ai to port 8901 on my testbench server (which has the Tailscale IP address 100.64.64.11), and handles the HTTPS certification.

🌐
Using Tailscale for the connection means I don't need to expose the underlying service to the public internet—the server doesn't need a public IP address, port forwarding rules, or open firewall ports. I plan to do a deep dive on Tailscale in a future post, but for now just know it adds an important layer of security and simplicity to this setup.

If you want to try this script out yourself, see the more detailed documentation at sij.ai/sij/cf, and by all means have a look at the Python code and see how it works under the hood.

Getting Started

  1. Start by installing Caddy on your server
  2. Create a config for just one website
  3. Let Caddy handle your HTTPS certificates
  4. Add more sites when you're ready

Start small, get comfortable with how it works, and expand when you need to. Ready to dig deeper? The Caddy documentation is excellent, or feel free to reach out with questions.