Apache config for self-hosted Lemmy instance with no Docker

I managed to compile a config to make Lemmy work with Apache. It took some time and a lot of googling. I don’t think it’s perfect, but it seems to work. I removed most of the information specific to my instance, but you’ll most likely still need to at least tweak the paths to make this work properly.

Why did I do that when there’s perfectly fine NGINX? Because it’s not fun when things just work. Also, NGINX isn’t in Slackware’s repo, and I decided to offload managing security fixes to Pat, that way I don’t depend on SBo, so I migrated from NGINX back to Apache. It was a painful process that clearly showed me why NGINX became the new de facto industry standard for web servers, but hey, I had some fun in the process of rewriting the configs. So, yay for unorthodox and unsupported solutions for problems that didn’t exist in the first place!

I also tried the recommended Docker approach to running stuff, and I didn’t like it. It was too confusing. There’s templates, containers, images… If a container crashes, it’s not obvious how to start it back up, because you need the container’s name with no indication on where to find it and all you have are images and a template that requires them. Maybe containers are good, but I personally dislike the idea of running a server that’s just a wrapper for N amount of containers, all of which have their own environment inside which potentially adds overhead when services running inside them could share resources instead, e. g. only one web server and not a myriad of them. Basically, docker-compose is evil.

Anyway, back to Apache.

The config expects LemmyUI to be at /var/www/lemmy/lemmy-ui and a path for Let’s Encrypt’s certbot to be /var/www/certbot. Obviously, you need to replace {{domain}}, {{lemmy_port}} and {{lemmy-ui_port}} with your values.


<VirtualHost *:80>
        ServerName {{domain}}
        Alias /.well-known/acme-challenge/ /var/www/certbot/.well-known/acme-challenge/
        <Directory /var/www/certbot/.well-known/acme-challenge>
            Require all granted
            AllowOverride none
            Options -Indexes -FollowSymLinks
        Redirect / https://{{domain}}/

<VirtualHost *:443>
        ServerAdmin webmaster@{{domain}}
        ServerName {{domain}}
        ErrorLog "/var/log/httpd/{{domain}}-error_log"
        CustomLog "/var/log/httpd/{{domain}}-access_log" common

        SSLEngine on
        SSLCertificateFile /etc/letsencrypt/live/{{domain}}/cert.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/{{domain}}/privkey.pem
        SSLCertificateChainFile /etc/letsencrypt/live/{{domain}}/chain.pem
        SSLProtocol -all +TLSv1.2 +TLSv1.3
        SSLHonorCipherOrder on
        SSLCipherSuite HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA
        SSLCompression off
        SSLSessionTickets off

        Protocols h2 http/1.1

        # Add compression for some text-based resources
        AddOutputFilterByType DEFLATE text/css
        AddOutputFilterByType DEFLATE application/javascript
        AddOutputFilterByType DEFLATE image/svg+xml

        # Add headers
        Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
        Header always set X-Frame-Options SAMEORIGIN
        Header always set X-Content-Type-Options nosniff
        Header always set X-XSS-Protection "1; mode=block"
        ProxyPreserveHost On
        ProxyAddHeaders On

        # Don't proxy LetsEncrypt's ACME challenge
        ProxyPass /.well-known/acme-challenge/ !
        Alias /.well-known/acme-challenge/ /var/www/certbot/.well-known/acme-challenge/
        <Directory /var/www/certbot/.well-known/acme-challenge>
            Require all granted
            AllowOverride none
            Options -Indexes -FollowSymLinks

        # Don't proxy static files, serve them directly instead
        ProxyPassMatch ^/(assets|js|styles|service-worker.js) !
        DocumentRoot /var/www/lemmy/lemmy-ui/dist
        <Directory /var/www/lemmy/lemmy-ui/dist>
            Require all granted

        # Proxy API calls
        ProxyPassMatch ^/(api|pictrs|feeds|nodeinfo|\.well-known)/(.*)$ ws://{{lemmy_port}}/$1/$2

        # ProxyPassReverse doesn't like regexps, so we have to manually specify every location
        ProxyPassReverse /api/ ws://{{lemmy_port}}/api/
        ProxyPassReverse /pictrs/ ws://{{lemmy_port}}/pictrs/
        ProxyPassReverse /feeds/ ws://{{lemmy_port}}/feeds/
        ProxyPassReverse /nodeinfo/ ws://{{lemmy_port}}/nodeinfo/
        ProxyPassReverse /.well-known/ ws://{{lemmy_port}}/.well-known/

        # Proxy the frontend
        ProxyPass /{{lemmy-ui_port}}/
        ProxyPassReverse /{{lemmy-ui_port}}/

        # Correctly proxy websocket traffic
        RewriteEngine On
        RewriteCond %{HTTP:Upgrade} websocket [NC]
        RewriteRule .* ws://{{lemmy_port}}%{REQUEST_URI} [P]

        # Proxy POST and JSON requests directly to the backend
        RewriteCond %{HTTP_ACCEPT} "=application/activity+json" [NC,OR]
        RewriteCond %{HTTP_ACCEPT} '=application/ld+json; profile="https://www.w3.org/ns/activitystreams"' [NC,OR]
        RewriteCond %{REQUEST_METHOD} ^POST [NC]
        RewriteRule .* ws://{{lemmy_port}}%{REQUEST_URI} [P]



It turned out, that I need a full featured website much less than a simple blog. I don’t want to have a blog on Tumblr, but I do like their implementation of it, so I went with Chyrp, which is pretty close. But then Chyrp died, so I moved to Hugo. The blog is NSFW, as I might occasionally post some pr0n and other disturbing stuff.