DIY cloud - Cows and Matrix

Annoyed of being a glass citizen? Follow this how-to to create your own private cloud 3/5

Part of 3 of getting away from Google, Apple and all those companies trying to sell your personal data. After Bookmark-sync and file-sync I also deployed a password-server. I tried Passk but ultimately went with Keeweb which is a PW-manager for Keepass.  

Installing Keeweb is a no-brainer. Simply install docker and run

docker run --name keeweb -d -p 443:443 -p 80:80 -v $EXT_DIR:/etc/nginx/external/ antelle/keeweb

Then create another caddy-entry and send all traffic to port 443. Next you will need to setup a small script to periodically transfer the certificate and key to your keeweb-server. The steps are simple:

  1. Create a new user on the keeweb-server and restrict the user to his home-folder
  2. Switch to root and create a cronjob (crontab -e) and copy over the key and cert to /etc/nginx/external/ every week (or at least every 3 months)
  3. Switch to caddy and create another cronjob which copies over the key and cert to the keeweb-server (, like so:
scp /var/lib/caddy/.local/share/caddy/certificates/ keyuser@

scp /var/lib/caddy/.local/share/caddy/certificates/ keyuser@

4. To be able to run this script you will need to create a ssh-key without passphrase to be able to login without password. To do so, switch to the caddy-server and run ssh-keygen -t rsa and use no passphrase.

Safe the key in the default location (hit enter) otherwise add the switch -i and the location of the created key in the script above.

5. Copy over the key to the keeweb-server with ssh-copy-id keyuser@ ( is the keyweb-server)

6. With all that done try to run the script. It should copy over 2 files to /home/keyuser

That is it :) Now you should have a running keeweb-server

Something with cows

With Bookmark-sync, file-sync and password-sync down, I want to tackle the more important but also more "complicated" parts of the whole journey to my own cloud.

First thing on the list is mail. Few people really realize how important mail actually still is. With 2FA, password-reset and account activation mail is usually the last resort before locking oneself out of an account. Just imagine, what would happen if your gmail-account got suspended today?

Would you still be able to access all your accounts in case you need to reset your password? I propably would not, which is another reason to take control of your mail-account by hosting your own mailserver, being in control of the whole domain. If you then still loose access or the data itself, you can at least blame it on yourself ;)

There are 2 aweseome projects (Mail-in-a-box, Mailcow) regarding mail, which include everything you could ask for when it comes to reading, sending and securing mail.

Some of the main features are:

  • DKIM
  • DNSSEC (with DANE TLSA if supported, check your DNS)
  • Nextcloud
  • Webmail
  • Spamfilter
  • Backup
  • and more

I like both of them and I would recommend you to try them both to see which one you prefer. For this case though, I went with Mailcow simply because it does not include extras that I do not need like Nextcloud.


Transfering mail requires a bit more todo on your side before you actually install anything on Proxmox. We will need to backup existing mail, setup opnsense, setup DNS and prepare caddy.


This one is simple and may take a while depending on the amount of mails we are talking about. If you are using clients like kmail or  thunderbird then make sure you save the mails offline to simply drag-and-drop them to your new account later on.


With all mails backed up, you will need to open a few holes in the firewall itself. Before we do that though, we need to make sure that some crucial things are taken care of.

The first few steps are similar to all the other VMs we installed before:

  1. Create new VLAN
  2. Attach VLAN to OpnSense and configure it
  3. Create new Ubuntu 20 minimal server and do basic installation

Now decide on the hostname for your mail-server. Other than the other VMs this is actually important as we will use this for reverse DNS later on and also as the URL for the Webmail. If you already gave your VM a name and want to change it then edit the following files

  • /etc/hosts
  • /etc/hostname

Next set up the correct timezone and make sure that NTP is working. On Ubuntu this is pretty simple with

timedatectl set-timezone Europe/Rome
timedatectl set-ntp true

Now browse to your OpnSense-Firewall and create a few NAT-Rules. We want to send all traffic needed for Mail directly to the new Mail-Server:

  • Destination: WAN-Net
  • Ports (check Screenshot)
  • Redirect: No redirect (use the same port)
  • Description: Port XXX to Mail-Server


With the Firewall set up, we now need to prepare the DNS itself. The mailcow-documentation is pretty easy to understand here and I recommend to simply set up your DNS with your provider according to the docs.

(Do not worry about DKIM at this point. Key will be created after running the setup)

Do not forget to set up the reverse dns for your domain as well. Example, I gave my Mail-VM the hostname "mail" and therefore set reverse dns to

When everything is done you can use tools like MX Toolbox to check if everything works as expected.


Fortunately caddy is pretty simple. We are going to setup the Webmail which is also our Admin-portal. I set up another A-record with "mail". With this add another entry in your Caddyfile like the following:

/etc/caddy/Caddyfile {

It is important here that you proxy all traffic to port 8080 which is the HTTP-Port of the docker-instance of Mailcow. If you want you can change it later on but it is not required.


Now install docker and docker-compose with apt install docker-compose docker -y and run docker run hello-world to see if everything is working fine.


Next run the following:

cd /opt
git clone
cd mailcow-dockerized

#generate config with fqdn (

nano mailcow.conf

Edit the file mailcow.conf and change the following:

  • (set to your mail-fqdn)
  • DBNAME & DBUSER (create something secure)
  • HTTP_PORT=8080
  • TZ=Europe/Rome (set to your timezone)
  • SKIP_LETS_ENCRYPT=y (this one is very important as we do not want extra certs)


Because we run mailcow behind caddy we need to make sure that mailcow is able to use the certificates as well. To do so, we need to create another 2 cronjobs and copy over the certificates periodically. For more detailed steps go back to the keeweb-installation at the top of this post and replace the keys and IPs accordingly.

Mailcow expects the certificate and key in the following path:




There is no need to convert the files from caddy. Simply renaming them from .crt to .pem works as well.


  1. Create new user (non-sudo) on mail-server and restrict to home-folder
  2. Create ssh-key for new user and copy it to caddy so caddy can automatically login without password on mail-server as new user
  3. Caddy: Create cronjob to scp and -key to home-folder of new user at mail-server
  4. Mail-server: Create cronjob to copy files from home-folder of new user to the mentioned folder in /opt/ for mailcow to use

With all that done you just need to run

docker-compose pull
docker-compose up -d

Afterwards you should be able to browse to your new mailcow-webgui at :)

Default login is admin:moohoo

The remaining setup is pretty self-explanatory so I will skip it and leave it up to the reader to figure out the rest ;)


While mails are important and should be secured, messaging and messaging apps can leak even more data that could be sold or used against you.

Whatsapp, Instagram, Line, Google Hangouts or oldschool apps like ICQ or MSN (anyone still using that?) are all known to collect and use your data. Switching apps is usually easy but convincing your friends and relatives to leave those apps is mostly a fruitless endeavour.

That means if you want to keep talking to those people you will need to keep Whatsapp or Instagram or Facebook or whatever messenger you really do not want to keep.

The solution? Bridging!

With Matrix Synapse Bridges we can sort of proxy a lot of different apps like Whatsapp, Facebook Messenger and Telegram and therefore get around installing those apps on smartphones or desktops.

In the end you will only need one app to connect to all those messengers and only need to worry about what you say on those apps. This will obviously not make the messaging itself any more secure but at least the apps itself do not need to be installed and do not get the permissions to look through your pictures, track your location and so on.

Note: Matrix Synapse supports a lot of different apps and has a ton of features. I only need it for bridging though which is why the setup is kept basic on purpose. Some things like Federation will not work (Matrix-Servers talking to each other) If this is something you need, you will need to fix those issues afterwards.


To install Matrix we need a jumphost running ansible and our actual Matrix-Server. The jumphost needs the following installed

  • Ansible
  • Root-Access to the Matrix-Server via ssh

The Matrix-Server needs the following:

  • CentOS7
  • SSH allow root-access (can be disabled later on)


As mentioned, the ansible-playbook is about to install a lot of different services which are disabled by default but just need to be turned on. For this reason I will simply copy & paste the whole DNS-Setup from the playbook without changing anything.


Next thing is the caddyfile. Like I mentioned before, Federation needs more than a simple entry and depending on your domain-setup this makes it a bit more complicated. I will keep it simple and use Matrix only for bridging which is why I only need the following: {


Now we need a few things for the playbook to work. Clone the playbook into any folder on your jumphost with

git clone

and then follow those steps from 1-5 and come back here. Then open up the file /inventory/host_vars/ once again and change or add the following lines:

# Example value:

# The Matrix homeserver software to install.
# See `roles/matrix-base/defaults/main.yml` for valid options.
matrix_homeserver_implementation: synapse

# A secret used as a base, for generating various other secrets.
# You can put any string here, but generating a strong one is preferred (e.g. `pwgen -s 64 1`).
matrix_homeserver_generic_secret_key: 'AAABBBCCCCDDDD'

# The playbook creates additional Postgres users and databases (one for each enabled service)
# using this superuser account.
matrix_postgres_connection_password: 'AAAABBBBBBCCCCCC'

# Do not retrieve SSL certificates. This shall be managed by another webserver or other means.
matrix_ssl_retrieval_method: none

# Do not try to serve HTTPS, since we have no SSL certificates.
# Disabling this also means services will be served on the HTTP port
# (`matrix_nginx_proxy_container_http_host_bind_port`).
matrix_nginx_proxy_https_enabled: false

matrix_coturn_enabled: false

# Trust the reverse proxy to send the correct `X-Forwarded-Proto` header as it is handling the SSL >
matrix_nginx_proxy_trust_forwarded_proto: true

# Trust and use the other reverse proxy's `X-Forwarded-For` header.
matrix_nginx_proxy_x_forwarded_for: '$proxy_add_x_forwarded_for'

#enable dimension
#matrix_dimension_enabled: true
#  - "@dimension:{{ }}"

#matrix_dimension_access_token: "AAAAAAAAAAAAAA"

#mautrix telegram bridge
#matrix_mautrix_telegram_enabled: true
#matrix_mautrix_telegram_api_id: 123123123
#matrix_mautrix_telegram_api_hash: SOME_API_HASH

#shared secret
#matrix_synapse_ext_password_provider_shared_secret_auth_enabled: true
#matrix_synapse_ext_password_provider_shared_secret_auth_shared_secret: #SECRET_BLA

Replace the "AAAABBBBCCCCC" and and with a password/secret (generate it yourself) and the others with your fqdn and full domain-name.

When that is done run the playbook with the following:

ansible-playbook -i inventory/hosts setup.yml --tags=setup-all --ask-pass

After entering the password for root (matrix-server) it should take a while to install everything. Beware that if any of the services installed do not start fast enough, you will get an error at the end of the installation. You can ignore those.

Create users

Next we want to create users. I would recommend to create 3 for now

  • Admin
  • Your user
  • Dimension-user

The command is always the same. All you need to change is "isAdmin" and the password and username:

ansible-playbook -i inventory/hosts setup.yml --extra-vars='username=<your-username> password=<your-password> admin=<yes|no>' --tags=register-user

Example, I created an "admin", "user1" and "dimension" with "admin" being the admin.

After running that command, go back to your vars.yml and uncomment the following lines and replace the shared_secret with something you created yourself:

#matrix_synapse_ext_password_provider_shared_secret_auth_enabled: true
#matrix_synapse_ext_password_provider_shared_secret_auth_shared_secret: #SECRET_BLA

Then rerun the playbook with the following command:

ansible-playbook -i inventory/hosts setup.yml --tags=setup-all,start --ask-pass


For the next steps we need a private Firefox/Chrome-Session

After all that you should be able to login as the user dimension at your matrix-portal at After logging in open up settings, go to Help&About and scroll down to API. There you should be able to find a token which we will need shortly.

Write down the token and then CLOSE (do not logout) the private browser-window. Go back to your vars.yml and change the following lines and replace the token you just wrote down.

#enable dimension
matrix_dimension_enabled: true
  - "@dimension:{{ }}"

matrix_dimension_access_token: "AAAAAAAAAAAAAA"

#mautrix telegram bridge
matrix_mautrix_telegram_enabled: true
matrix_mautrix_telegram_api_id: 123123123
matrix_mautrix_telegram_api_hash: SOME_API_HASH

Telegram Bot

Now we need to create a Telegram Bot which you can do here

After going through the setup, write down the api_id and api_hash and replace them accordingly in vars.yml. With all that done run the playbook again:

ansible-playbook -i inventory/hosts setup.yml --tags=setup-all,start --ask-pass

Beware, everytime from this point on the playbook threw an error about at least one (or more) services not starting. If that happens manually check the corresponding service on the matrix-server. In my case it just took a bit longer to actually start but worked fine.

Last step

After all that you need to log back in (as your normal user) to matrix and then start a new chat with @telegrambot:YOUR_DOMAIN. In the chat then type "login" and follow the prompts. If everything worked out you should be able to see your chats and groups popping in.

More services

With all that you should now be able to chat and message over telegram via Element (the matrix-client). If you need voice and video-calls you will need to invest a bit more effort to get the rest of matrix working.

After Telegram I also added bridges for

  • Whatsapp
  • Signal
  • Discord
  • Slack
  • Facebook Messenger

The steps are mostly the same for all of them and are described in great detail in the ansible-playbook. Just beware that because of the basic setup most "Matrix-Check-Tools" will not work (fail) and not help you in analyzing any issues. If you want a full matrix-server with federation, video-calls and so on, you should follow the whole playbook from start to finish.

Nevertheless, if you followed all the steps up to this point then you should now have at least the following services running:

  • Mail
  • Messenger (Matrix)
  • Bookmark-Sync
  • Password-Manager
  • File-share

By now it should be clear on how to add new services and what obstacles you are likely to face. I encourage you to try and find more services you could host yourself. Simply going through posts of other people may also give you ideas on what you would like to install yourself.

Regarding this blog I will take a closer look on how to secure those services we just set up and how to harden them a bit more before moving on to replacing the actual hardware and software we are working on daily in the post after.

To be continued...