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.
lemmy.conf:
<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
</Directory>
Redirect / https://{{domain}}/
</VirtualHost>
<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
</Directory>
# 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
</Directory>
# Proxy API calls
ProxyPassMatch ^/(api|pictrs|feeds|nodeinfo|\.well-known)/(.*)$ ws://127.0.0.1:{{lemmy_port}}/$1/$2
# ProxyPassReverse doesn't like regexps, so we have to manually specify every location
ProxyPassReverse /api/ ws://127.0.0.1:{{lemmy_port}}/api/
ProxyPassReverse /pictrs/ ws://127.0.0.1:{{lemmy_port}}/pictrs/
ProxyPassReverse /feeds/ ws://127.0.0.1:{{lemmy_port}}/feeds/
ProxyPassReverse /nodeinfo/ ws://127.0.0.1:{{lemmy_port}}/nodeinfo/
ProxyPassReverse /.well-known/ ws://127.0.0.1:{{lemmy_port}}/.well-known/
# Proxy the frontend
ProxyPass / http://127.0.0.1:{{lemmy-ui_port}}/
ProxyPassReverse / http://127.0.0.1:{{lemmy-ui_port}}/
# Correctly proxy websocket traffic
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule .* ws://127.0.0.1:{{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://127.0.0.1:{{lemmy_port}}%{REQUEST_URI} [P]
</VirtualHost>