Introduction
This procedure describes how Alexis Huxley set up a XMPP server at home. That this is at home is important: I have several servers using private IP addresses, but my ISP allocates one internet-accessible IP address to my router. This procedure is designed with that in mind.
If you do not want support for BOSH then skip all sections with BOSH in their titles. Here is more information about BOSH, but think of it as XMPP over HTTP(S).
If you do need support for an Apache reverse proxy then skip all sections with ‘without an Apache reverse proxy’ in their titles.
Conversely, if you don’t need support for an Apache reverse proxy then skip all sections with ‘with an Apache reverse proxy’ in their titles.
All commands are to be run as root unless specified otherwise.
Environment variables
The procedure refers to the following environment variables and assumes they are already set, so be sure to set them in a timely fashion.
- Set some environment variables:
ALLOW_REGISTRATION=false # my server is not for public use FRONTENDVHOST=<xmpp-server-public-name> # e.g. FRONTENDVHOST=jabber.pasta.freemyip.com FRONTENDVPROT=http FRONTENDVPORT=80
- If there is a reverse proxy then set some more environment variables:
HTTP_REDIRECTS_TO_HTTPS=false # cert needed on backend, so certbot runs on backend, so http on backend must be reachable FRONTENDVHOST_RUNS_CERTBOT=false # needs special opts so not done by sub-procedure FRONTENDHOST=<apache-reverse-proxy-local-name> # e.g. FRONTENDHOST=armonie.pasta.net BACKENDHOST=<xmpp-server-local-name> # e.g. BACKENDHOST=jaborini.pasta.net BACKENDVHOST=$BACKENDHOST BACKENDVPROT=http BACKENDVPORT=80
Initial installation of Prosody
- Run:
apt -y install prosody prosody-modules systemctl stop prosody
- Stop Prosody from attempting to use IPv6 by editing /etc/prosody/prosody.cfg.lua and setting:
interfaces = { "*" } local_interfaces = { "*" }
- I had very bad performance transferring files, so I stopped Prosody from applying limits by editing /etc/prosody/prosody.cfg.lua and commenting out:
... modules_enabled = { ... -- "limits"; ...
- Remove the localhost configuration as this is not needed with:
sed -i -r 's/^(VirtualHost "localhost")/-- \1/' /etc/prosody/prosody.cfg.lua rm -f /etc/prosody/conf.d/localhost.cfg.lua
Overview of Prosody/XMPP without Apache reverse proxy
The sections below with “Prosody/XMPP” in their titles are intended to establish this configuration without a reverse proxy:
Note that:
- XMPP clients and servers connect to the prosody process on different ports
- XMPP clients and servers require that the prosody process encrypt communications using an SSL certificate
- we get the SSL certificate from letsencrypt.org
- that necessitates having certbot listening on port 80 on the prosody box to respond to ownership challenges from letsencrypt.org
- the router is required to forward all incoming connections to the right box
- from the internet, both XMPP clients and web clients (specifically Lets Encrypt SSL certificate challenger) will use the name $FRONTENDVHOST: it is up to the router, based on the port, to direct XMPP traffic to the Prosody box and to direct HTTP traffic to certbot
- from the home network, only XMPP clients will use the name $FRONTENDVHOST; it is up to the local DNS to direct (XMPP) traffic to the Prosody box.
Firewall for Prosody/XMPP without Apache reverse proxy
- Set up the following rules on the firewall:
firewall port destination host destination port purpose 5222 prosody box 5222 client to server XMPP traffic 5269 prosody box 5269 server to server XMPP traffic 80 prosody box 80 Lets Encrypt SSL certificate challenge
Overview of Prosody/XMPP with Apache reverse proxy
Note that:
- See the notes in the ‘Overview of Prosody/XMPP without Apache reverse proxy’ section above
- it is up to the router, based on the port, to direct XMPP traffic to the Prosody box and to direct HTTP traffic to Apache reverse proxy
Firewall for Prosody/XMPP with Apache reverse proxy
- Set up the following rules on the firewall:
firewall port destination host destination port purpose 5222 prosody box 5222 client to server XMPP traffic 5269 prosody box 5269 server to server XMPP traffic 80 Apache reverse proxy 80 Lets Encrypt SSL certificate challenge
DNS for Prosody/XMPP without Apache reverse proxy
- Ensure that from the internet, $FRONTENDVHOST resolves to the IP address of router’s interface on the internet (the firewall will then forward both XMPP and HTTP traffic to the prosody box).
- Ensure that from the home network, the same name resolves to the IP address of the prosody box (some hints on how to do this can be found here).
DNS for Prosody/XMPP with Apache reverse proxy
- Ensure that from the internet, $FRONTENDVHOST resolves to the IP address of router’s interface on the internet (and the firewall will then forward XMPP traffic to the prosody box and HTTP traffic to the Apache reverse proxy).
- Ensure that from the home network, the same name resolves to the IP address of the prosody box (some hints on how to do this can be found here).
Certificates for Prosody/XMPP without Apache reverse proxy
- Run:
apt -y install certbot
- Run certbot.
certbot certonly --standalone -d $FRONTENDVHOST
(The option
--standalone
option will – whenever requesting/renewing a certificate – start a standalone internal webserver to allow letsencrypt.org authenticate the renewal request; /etc/cron.d/certbot will respect this option when it is used to initially request the certificate.)
Certificates for Prosody/XMPP with Apache reverse proxy
- Log in to the Apache reverse proxy.
- Complete configuring a reverse proxy (revision 1).
- Log in to the prosody box.
- Run:
apt -y install certbot
- Run certbot.
certbot certonly --standalone -d $FRONTENDVHOST
(The option
--standalone
option will – whenever requesting/renewing a certificate – start a standalone internal webserver to allow letsencrypt.org authenticate the renewal request; /etc/cron.d/certbot will respect this option when it is used to initially request the certificate.)
Configuring Prosody/XMPP
- Create a minimal new virtual host by running:
cat > /etc/prosody/conf.avail/$FRONTENDVHOST.cfg.lua <<EOF VirtualHost "$FRONTENDVHOST" ssl = { certificate = "/var/lib/prosody/$FRONTENDVHOST.fullchain.pem"; key = "/var/lib/prosody/$FRONTENDVHOST.privkey.pem"; } allow_registration = "$ALLOW_REGISTRATION"; EOF ln -sr /etc/prosody/conf.avail/$FRONTENDVHOST.cfg.lua /etc/prosody/conf.d
- Certbot has put the certificate and key in /etc/letsencrypt/live/$FRONTENDVHOST/. Certbot supports hooks to inform other programs about the new certificate at certificate renewal time.
prosodyctl --root cert import <cert-dir>
, run as root (as certbot is and so its hooks are as well) should have the rights to read the well-protected certificate directory:jaborini# prosodyctl --root cert import /etc/letsencrypt/live/jabber.pasta.freemyip.com Imported certificate and key for hosts jabber.pasta.freemyip.com jaborini#
However, it does not copy the certificates to where the configuration file says they are to be loaded from, so it is clearer to simply copy the files and reload prosody. So now we create a simple script to run this at renewal time:
cat > /etc/letsencrypt/renewal-hooks/deploy/prosody <<EOF #!/bin/bash set -e # Copy certificate to where prosody can read it. cp /etc/letsencrypt/live/$FRONTENDVHOST/fullchain.pem /var/lib/prosody/$FRONTENDVHOST.fullchain.pem cp /etc/letsencrypt/live/$FRONTENDVHOST/privkey.pem /var/lib/prosody/$FRONTENDVHOST.privkey.pem chown prosody:prosody /var/lib/prosody/$FRONTENDVHOST.fullchain.pem chown prosody:prosody /var/lib/prosody/$FRONTENDVHOST.privkey.pem systemctl reload prosody EOF chmod 755 /etc/letsencrypt/renewal-hooks/deploy/prosody
(Note that although no commands in this script require bash as the execution shell yet, later in this procedure commands will be added that do.)
- If migrating to a new server then copy over account information, which is stored in /var/lib/prosody/{accounts,roster,vcard}, remembering to set the owner & group correctly.
- Restart the service by running:
service prosody restart
- Verify that the certbot plugin works by running:
/etc/letsencrypt/renewal-hooks/deploy/prosody
Creating accounts
- Run:
echo $ALLOW_REGISTRATION
and if that displays
true
then skip this section. - IAs root run:
prosodyctl adduser <username>@$FRONTENDVHOST
Configuring Pidgin with XMPP
- Select Accounts –> Manage Accounts –> Add.
- Fill in the details using $FRONTENDVHOST, as shown in this example:
- Note that:
- Domain is the value of $FRONTENDVHOST
- Resource is a description of the client’s location
- depending on whether registration is allowed or not, you either provide a password or click ‘Create this new account on the server’
- Set ‘Remember password’ and ‘Use this buddy icon …’ as you see fit.
- Add yourself as a buddy (this is helpful for testing) and then send yourself a message! 🙂
Overview of Prosody/BOSH without Apache reverse proxy
Firewall for Prosody/BOSH without Apache reverse proxy
- The firewall needs to allow:
firewall port destination host destination port purpose 5280 prosody box 5280 client to server BOSH-over-http traffic 5281 prosody box 5281 client to server BOSH-over-https traffic 80 prosody box 80 SSL certificate renewal challenge
Certificates for Prosody/BOSH without Apache reverse proxy
The SSL certificate is already on the prosody box as a result of completing “Certificates for Prosody/XMPP” above.
Configuring Prosody/BOSH without Apache reverse proxy
Note that some of this is indentical to the section ‘Configuring Prosody/BOSH with Apache reverse proxy’ below.
- Edit /etc/prosody/prosody.cfg.lua uncomment the bosh module and tell bosh where the certificate is:
... modules_enabled = { ... "bosh"; ... } ... https_ssl = { certificate = "/var/lib/prosody/jabber.pasta.freemyip.com.fullchain.pem"; key = "/var/lib/prosody/jabber.pasta.freemyip.com.privkey.pem"; }
but with the correct hostname. Note that it is correct that this is specified in the global section.
- Run:
systemctl restart prosody
- Test as follows:
- Use
ss
to verify the listening ports, as in this example:jaborini# ss -tlpn | grep 52 LISTEN 0 128 0.0.0.0:5222 0.0.0.0:* users:(("lua5.4",pid=3462,fd=13)) LISTEN 0 128 0.0.0.0:5281 0.0.0.0:* users:(("lua5.4",pid=3462,fd=12)) LISTEN 0 128 0.0.0.0:5280 0.0.0.0:* users:(("lua5.4",pid=3462,fd=11)) LISTEN 0 128 0.0.0.0:5269 0.0.0.0:* users:(("lua5.4",pid=3462,fd=8)) jaborini#
- Use w3m to access the BOSH url over http and https, as in this example:
damson$ w3m -dump http://$FRONTENDVHOST:5280/http-bind Prosody BOSH endpoint It works! Now point your BOSH client to this URL to connect to Prosody. ⚠ This endpoint is not considered secure! ⚠ damson$ w3m -dump https://$FRONTENDVHOST:5281/http-bind Prosody BOSH endpoint It works! Now point your BOSH client to this URL to connect to Prosody. damson$
Note that that the output from the first command (“It works! … not secure!” and the warning triangles) is very misleading! It does not work because the server won’t to talk on an unencrypted connection. If you really want to support BOSH/http (i.e. unencrypted) then modify /etc/prosody/prosody.cfg.lua and add the following line:
consider_bosh_secure = "true";
and restart prosody. (There is a valid use case for this option: when the client’s connection to the reverse proxy is encrypted and the reverse proxy’s connection to prosody is not.)
- Use
Configuring Pidgin with BOSH/http over the standard BOSH/http port
- Depending on whether you have already created your account or not then either add or modify your account (see above for details) and then go to the Advanced tab.
- Fill in the details using $FRONTENDVHOST, as shown in this example:
- Note that:
- BOSH URL: is http://$FRONTENDVHOST:5280/http-bind
- Connection port: 0 (the real port number must be encoded in the BOSH URL!!!)
- Connection security: If set to ‘Require encryption’ (the default) then pidgin will report “You require encryption, but no TLS/SSL support was found” and will not connect.
- Connect and send yourself a test message.
Configuring Pidgin with BOSH/https over the standard BOSH/https port
- Depending on whether you have already created your account or not then either add or modify your account (see above for details) and then go to the Advanced tab.
- Fill in the details using $FRONTENDVHOST, as shown in this example:
- Note that:
- BOSH URL: is https://$FRONTENDVHOST:5281/http-bind
- Connection port: 0 (the real port number must be encoded in the BOSH URL!!!)
- Connect and send yourself a test message.
Overview of Prosody/BOSH with Apache reverse proxy
In principle, this is what we want:
Note that:
- Apache must decide whether to forward an incoming request to prosody or to certbot (the
ProxyPass
directive can do this) - Certbot could be run on the reverse proxy, but I choose to run it on the prosody box, in simpler configurations, only the prosody box needs a certificate
Firewall for Prosody/BOSH with Apache reverse proxy
- Since there are other vhosts, it is assumed that the firewall already forwards traffic on 80 and 443 to the Apache reverse proxy, so no new firewall rules are required.
- However, depending on whether BOSH fulfils all your needs or not, then following rules could be removed:
firewall port destination host destination port purpose 5222 prosody box 5222 client to server XMPP traffic
Certificates for Prosody/BOSH with Apache reverse proxy
The SSL certicate needs to be installed on two hosts:
- the prosody box (because the prosody process will want to provide SSL connections for clients connecting to $FRONTENDVHOST:5222, $FRONTENDVHOST:5269)
- the reverse proxy (because it is the end point of a client’s connection to $FRONTENDVHOST:443)
As explained above, I decided that the prosody box should manage certificate renewal because in simpler configurations, only the prosody box needs a certificate (though this isn’t much of a reason). This means:
- the prosody box must run certbot (we already did this as a certificate is required for prosody/XMPP)
- the reverse proxy, listening on port 80 must:
- send BOSH/http traffic to the prosody server listening on port 5280 on the prosody box
- send SSL challenges to certbot listening on port 80 on the prosody box (remember: earlier we ran
certbot --standaone
) on port 80 on the prosody box
(we could do this with
ProxyPassMatch
rules), or:- send all traffic to certbot listening on port 80 on the prosody (and BOSH/http won’t be supported)
- certbot on the prosody box must:
- copy the updated certificate to where the prosody server can read it and tell the prosody server to reload it (we already set up a certbot hook to do this several sections earlier)
- copy the updated certificate to to the reverse proxy (we could extend the certbot hook to do this)
SSL challenges look like this:
armonie# sed -nr 's/.*"(GET \/\.well-known\/[^ ]*).* 200 .*/\1/p' /var/log/apache2/*/*.log | uniq | head -3 GET /.well-known/acme-challenge/6hDPcYg-SZK9_Y7gXUhqmN2S74yrgjyEpHFR1JeQkk8 GET /.well-known/acme-challenge/IE0rE9Nq_m2Wv3AyJjWihuTyqTyqQOW-0iF9zPPYHto GET /.well-known/acme-challenge/eqWUF9ICLXteTSwCn3dfTd_nsODt7YGOmDsUuk6vR3Y armonie#
So, BOSH traffic and SSL challenges can easily be distinguished.
- Currently the reverse proxy forwards all requests on port 80 to certbot on the prosody box because of these directives in /etc/apache2/sites-available/$FRONTENDVHOST.conf:
ProxyPass / http://jaborini.pasta.net:80/ ProxyPassReverse / http://jaborini.pasta.net:80/
(jaborini.pasta.net is my Prosody box.)
However, we need to restrict the scope of this proxing so that only SSL challenges go to certbot. So change the above lines to something like this:
ProxyPass /.well-known/acme-challenge/ http://jaborini.pasta.net:80/.well-known/acme-challenge/ ProxyPassReverse /.well-known/acme-challenge/ http://jaborini.pasta.net:80/.well-known/acme-challenge/
- Additionally, to avoid non-matching URLs listing /var/www/html, run:
chmod 000 /var/www/html
- Run:
systemctl reload apache2
- To test do the following:
- On the prosody box run a simple web server using
socat
:socat -v -d -d TCP-LISTEN:80,crlf,reuseaddr,fork SYSTEM:" echo HTTP/1.1 200 OK; echo Content-Type\: text/plain; echo; echo \"this is fake certbot replying\"; "
(This command was based on the one here.)
- From somewhere on the internet run these two w3m commands and note the output: requests that don’t look like SSL challenges are rejected because the bosh module is not listening on 5280; requests that do look like SSL challenges are accepted and
socat
replies:damson$ w3m -dump http://$FRONTENDVHOST/ Forbidden You don't have permission to access this resource. damson$ w3m -dump http://$FRONTENDVHOST/.well-known/acme-challenge/blah this is fake certbot replying damson$
Note that that first request never makes it as far as the prosody box: it reaches the correct vhost, but as that has no
DocumentRoot
defined, it tries to look in /var/www/html, which it does not have permission to do. - Kill the
socat
process.
- On the prosody box run a simple web server using
- Set up the ssh access from the prosody box to the reverse proxy as follows:
- On the prosody box run:
ssh-keygen -qt ed25519 -N "" -f ~/.ssh/id_ed25519_certxfer
- Append the just-generated public key to root’s ~/.ssh/authorized_keys on the reverse proxy. (Note to myself: remember that PCMS will reset this file, so the key needs to be added to
gen-facility-local
). - Test by calling ssh as in the following example:
jaborini# ssh -i ~/.ssh/id_ed25519_certxfer $FRONTENDHOST hostname armonie jaborini#
- On the prosody box run:
- Run the following commands to add the lines necessary to make the existing certbot hook copy the renewed SSL certificate over the reverse proxy:
cat >> /etc/letsencrypt/renewal-hooks/deploy/prosody <<EOF # Copy XMPP/SSL certificate to reverse-proxy so it can use it for BOSH/https. rsync -e "ssh -i ~/.ssh/id_ed25519_certxfer" -va --delete /etc/letsencrypt/live/$FRONTENDVHOST/ $FRONTENDHOST:/etc/letsencrypt/live/$FRONTENDVHOST/ rsync -e "ssh -i ~/.ssh/id_ed25519_certxfer" -va --delete /etc/letsencrypt/archive/$FRONTENDVHOST/ $FRONTENDHOST:/etc/letsencrypt/archive/$FRONTENDVHOST/ { date echo echo "This certificate was updated by a certbot hook running on \$(hostname -f)!" echo "See https://www.pasta.freemyip.com/computing/procedures/configuring-xmpp-services/configuring-xmpp-services-revision-3/." } | ssh -i ~/.ssh/id_ed25519_certxfer $FRONTENDHOST "cat > /etc/letsencrypt/live/$FRONTENDVHOST/README.Alexis" ssh -n -i ~/.ssh/id_ed25519_certxfer $FRONTENDHOST systemctl reload apache2 EOF
Note that:
- since we are appending these lines to an existing hook that first line is with
>>
- it’s questionable if we should copy the certificate to /etc/letsencrypt/ on the reverse proxy, as, althrough the key was generated by certbot, it is not managed by certbot on the reverse proxy
- since we are appending these lines to an existing hook that first line is with
- Edit /etc/letsencrypt/renewal-hooks/deploy/prosody and make really really sure that the target vhost name is set correctly (i.e. that you didn’t mistype the ‘FRONTENDHOST’ variable name); otherwise all your certificates will be removed!
- Test the hook by calling it directly:
/etc/letsencrypt/renewal-hooks/deploy/prosody || echo FAILED
and, if everything is ok, then you can remove the
-v
option on the tworsync
calls.
Configuring Prosody/BOSH with Apache reverse proxy
- Complete ‘Configuring Prosody/BOSH without Apache reverse proxy’ above.
- Having already completed ‘Certificates for Prosody/BOSH with Apache reverse proxy’ above, the the proxying of port 80 on the reverse proxy through to port 5280 on the prosody box is almost configured. We only need to add the following lines to /etc/apache2/sites-available/$FRONTENDVHOST.conf after the already-present ProxyPass lines:
ProxyPass / http://jaborini.pasta.net:5280/ ProxyPassReverse / http://jaborini.pasta.net:5280/
It is essential that these lines are after the already-present ProxyPass lines: first match wins so longer URLs should go first!
- Run:
FRONTENDVPROT=https FRONTENDVPORT=443 BACKENDVPROT=https BACKENDVPORT=5281
(All other variable settings are still applicable.)
- Complete configuring a reverse proxy (revision 1).
- Edit /etc/apache2/sites-available/$FRONTENDVHOST-ssl.conf and comment out the snake-oil-certificate-related lines and uncomment the letsencrypt-certificate-related lines. (We can do this without just having applied for a letsencrypt certificate because the prosody box’s certbot’s hook has taken care of copying it over.)
- Since the reverse proxy not only connects to $BACKENDVHOST, but also uses that name in its requests and prosody doesn’t know that it may be connected to using that name, there is a mismatch between the names being used, which will result in prosody reporting
Unknown host: jaborini.pasta.net
. There are two ways to solve this: one is to use thehttp_host
assignment in /etc/prosody/conf.avail/$FRONTENDVHOST.cfg.lua (see here) but a nicer one is just to tell Apache to tell connect to one host, but use a different name in its requests. Add this to the http vhost configuration file:RequestHeader set Host "jabber.pasta.freemyip.com" RequestHeader set X-Forwarded-Proto: http ProxyPreserveHost On
and this to the https configuration file:
RequestHeader set Host "jabber.pasta.freemyip.com" RequestHeader set X-Forwarded-Proto: https ProxyPreserveHost On
- Run:
systemctl reload apache2
Configuring Pidgin with BOSH/http over the standard (non-BOSH) http port
Remember:
-
- this will only work if you set:
consider_bosh_secure = "true";
- if using an Apache reverse proxy, then DNS will need changing in order direct clients to that rather than directly to the prosody box.
- this will only work if you set:
- Depending on whether you have already created your account or not then either add or modify your account (see above for details) and then go to the Advanced tab.
- Fill in the details using $FRONTENDVHOST, as shown in this example:
- Note that:
- BOSH URL: is http://$FRONTENDVHOST:80/http-bind
- Connection port: 0 (the real port number must be encoded in the BOSH URL!!!)
- Connection security: If set to ‘Require encryption’ (the default) then pidgin will report “You require encryption, but no TLS/SSL support was found” and will not connect.
- Connect and send yourself a test message.
Configuring Pidgin with BOSH/https over the standard (non-BOSH) https port
Remember:
-
- if using an Apache reverse proxy, then DNS will need changing in order direct clients to that rather than directly to the prosody box.
- Depending on whether you have already created your account or not then either add or modify your account (see above for details) and then go to the Advanced tab.
- Fill in the details using $FRONTENDVHOST, as shown in this example:
- Note that:
- BOSH URL: is https://$FRONTENDVHOST:443/http-bind
- Connection port: 0 (the real port number must be encoded in the BOSH URL!!!)
- Connect and send yourself a test message.