Reverse Proxy


The task is to have a secure reverse proxy server for Synapse Matrix Protocol Server to handle external traffic, as to not expose Synapse directly for security purposes. There are several documented FOSS web servers to choose from, but since we had lighttpd for use aside from Apache, and it was undocumented, it became a challenge to undertake, and contribute back to upstream documentation.

While many use nginx as the go to, there is still purpose and utility for other web servers such as lighttpd. lighttpd has very low CPU usage and extremely low memory usage, potentially using less memory than all other web servers, which makes it ideal for embedded or memory constrained environments. While CPU and memory were not the primary factors, they were a consideration, and are selling factors for using lighttpd vs another.

The problem still remained that lighttpd was an undocumented reverse proxy for Synapse, and some things could not be done as they were in other web servers, such as nginx ability to serve up JSON data from configuration, and it is unknown if lighttpd can do the same. Therefore, a novel work around solution was produced using URL rewrite and external JSON files on disk.

Now we cannot just stop there, you can proxy one of two ways, TCP/IP port or Unix file socket, with Unix socket file communication being much faster and lower overhead than port. Therefore, we configured lighttpd to be a reverse proxy for Synapse via Unix file socket communication. The following is an overview of the configuration.

lighttpd configuration


The following lighttpd configuration snippets document Matrix protocol homeserver proxy services over IPv4 and IPv6, with HTTP for certificate renewal and some homeserver location URL endpoints, and with primary communication over secure HTTPS using TLS and ultra-fast socket file communication to Synapse.

Configuration starts with common settings in /etc/lighttpd/lighttpd.conf for including necessary modules and permissions for lighttpd. There are more settings, but they are command unrelated to the task at hand, so excluded for brevity.

server.modules = (
    "mod_rewrite",
    "mod_redirect",
    "mod_access",
    "mod_setenv",
    "mod_openssl",
    "mod_proxy",
    "mod_accesslog"
)

server.username      = "lighttpd"
server.groupname     = "lighttpd"

TCP/IP


We set server.use-ipv6 to “disable” and use IPv6 [::] explicitly below, in order to listen on all incoming IPv6 addresses. If you only want to listen on specific IPv6 addresses, set this to “enable” and specify said addresses below.

server.use-ipv6 = "disable"

Common file settings for secure socket layer (SSL), mostly transport layer security (TLS) these days.

ssl.pemfile = "/etc/lighttpd/cert+privkey.pem"
ssl.ca-file = "/etc/lighttpd/fullchain.pem"

Port settings, for both insecure and secure communication, where communication on insecure ports is redirected to communication on secure, forcing all communication to be secure, which requires a valid certificate, even for renewals with Let's Encrypt certbot; something to keep in mind.

Otherwise enabling TLS on a IPv4 or IPv6 port is rather straight forward and trivial common settings, with the addition of port 8448 for Matrix protocol Federation with others Matrix homeservers.

# redirect HTTP traffic to HTTPS, same for IPv6 below
$SERVER["socket"] == "0.0.0.0:80" {
    url.redirect = (
        "" => "https://${url.authority.noport}${url.path}${qsa}"
    )
}
$SERVER["socket"] == "0.0.0.0:443" { ssl.engine = "enable" }
$SERVER["socket"] == "0.0.0.0:8448" { ssl.engine = "enable" }
$SERVER["socket"] == "[::]:80" {
    url.redirect = (
        "" => "https://${url.authority.noport}${url.path}${qsa}"
    )
}
$SERVER["socket"] == "[::]:443" { ssl.engine = "enable" }
$SERVER["socket"] == "[::]:8448" {  ssl.engine = "enable" }

Unix Socket Communication


There are two ways to reverse proxy traffic to and/from Synapse Matrix protocol server, either using regular TCP/IP ports, or via ultra-fast Unix File Socket, which eliminates the TCP/IP overhead between lighttpd and Synapse. This is the most efficient way to reverse proxy, and makes it such that the proxy server and application are one. The primary URL endpoints used by the Synapse are /_matrix/, /_synapse/admin/, and /_synapse/client/, all traffic on those URL endpoints are mapped to Synapse via Unix file socket.

# both lighttpd and synapse need permissions for socket r/w
$HTTP["url"] =~ "(/_matrix|_synapse/admin|/_synapse/client)" {
    proxy.balance = "hash"
    proxy.server = ( 
        "" => ( 
            "backend-socket" => (
                "host" => "/var/lib/synapse/main_public.sock",
                "port" => 0
            )
        )
    )
    proxy.forwarded = (
        "for" => 1,
        "proto" => 1,
        "host" => 1,
    )
}

Usage of Unix file socket does require the socket file to have permissions for both lighttpd and Synapse to have read and write access, which can be accomplished by adding either to the other's group and ensuring the socket is created with the proper group permissions. In this case, lighttpd was added to synapse user group, with the socket file residing under a synapse managed directory.

Protect Admin


The Matrix protocol has an administration API that is available on the following endpoint, /_synapse/admin/, and rather than allow full access from anywhere, it has been restricted to only allow access from non-publicly routable, private, internal IPv6 ULA only. This makes the API access extremely secure, as it requires a valid internal ULA to connect.

# protect admin access IPv6 ULA only
$HTTP["remoteip"] !="fd00::/8" {
    $HTTP["url"] =~ "^/_synapse/admin/" {
        url.access-deny = ( "" )
    }
}

This is optional and not required, but access to the admin API should be restricted in some form.

Delegation of incoming federation traffic


The following is way to serve JSON formatted data from files without the file extension. Some web servers such as nginx have the ability to serve up data directly from a configuration file, as if it existed in a file. With lighttpd lacking such, or lacking the knowledge to do such with lighttpd, an alternative method was derived using URL rewriting. In this case, the data would reside in JSON formatted datafiles as normal, and those files will be provided as urls without the .json file extension, and when the URL without file extension is visited, the URL is rewritten to the the valid server destination that includes the .json file extension, providing the JSON formatted data.

# serve json files as urls. is there a better way?
url.rewrite-once = (
    "^/\.well-known/matrix/client$" => "/.well-known/matrix/client.json",
    "^/\.well-known/matrix/server$" => "/.well-known/matrix/server.json"
)

That addresses serving up the data on specific URLs, but does not handle the file MIME type, which is the type of data, something that can be determined via the first few bites or the file extension that was removed. Therefore, we must set the mime type, and we do so after the URL has been rewritten. There is a final entry of access control to allow the content to be accessed from any origin.

# This condition intentionally matches the post-rewrite URLs.
$HTTP["url"] =~ "^/\.well-known/matrix/(client|server)\.json$" {
    mimetype.assign = ( ".json" => "application/json" )
    setenv.set-response-header = ( "Access-Control-Allow-Origin" => "*" )
}        

Completed


Task completed, the JaxLUG's Synapse Matrix Protocol server, matrix.jaxlug.ngo, is online and ready for usage, come join and interact with us on our Matrix server and explore the Matrix universe!

Such contributions were provided to upstream in a Pull Request (PR), in Synapse PR #19875, as an example of how the JaxLUG contributes to FOSS, not just consuming! This documentation addition PR has been merged and will be part of future documentation. Thanks to the JaxLUG, lighttpd is no longer an undocumented reverse proxy for Synapse Matrix protocol server!