← Back to Blog
Setting up a Matrix Server using Ansible and Docker
8/25/2024
Updated: 9/29/2026
[Matrix]() is an open, decentralized communication protocol that can be self-hosted. The most popular homeserver implementation is [Synapse]().
Running Synapse manually is possible, but for a complete production setup there are many components to configure: PostgreSQL, reverse proxying, TLS certificates, media storage, TURN for voice/video calls, and more.
A convenient way to manage all of this is [matrix-docker-ansible-deploy](). It uses Ansible to configure the server and Docker to run the individual services.
This guide covers the initial setup as well as some important administration and maintenance tasks that are easy to overlook.
---
## What the setup provides
A typical installation can contain:
- Synapse — the Matrix homeserver
- PostgreSQL — the database
- Element Web — a Matrix client
- Traefik — reverse proxy and HTTPS
- Coturn — TURN server for VoIP and video calls
- Docker — container runtime
- Ansible — server configuration and deployment
The exact services depend on your configuration.
---
# 1\. Requirements
You need a Linux server with:
- A public IP address
- A domain name
- SSH access
- Python 3
- Git
- Ansible
- Docker
- Enough disk space
The server should also have a user that can execute commands through `sudo`.
Test this before starting the Matrix deployment:
```
sudo -n true
echo $?
```
If the result is:
```
0
```
the current user can use passwordless sudo.
This is useful because Ansible may need to become root while gathering system information and configuring services.
If Ansible reports:
```
sudo: a password is required
```
the SSH connection itself is working, but Ansible cannot perform the required privileged operations.
You can either configure passwordless sudo or run Ansible with become-password prompting.
---
# 2\. Download the playbook
Clone the repository:
```
git clone https://github.com/spantaleev/matrix-docker-ansible-deploy.git
cd matrix-docker-ansible-deploy
```
Check the available commands:
```
just --list
```
The `justfile` provides shortcuts for common operations such as:
```
install-all
setup-all
start-all
stop-all
register-user
update
```
The project changes over time, so always check the documentation and example configuration included with the version you are using.
---
# 3\. Configure the inventory
Create the inventory directory:
```
mkdir -p inventory
```
Copy the example inventory:
```
cp examples/hosts inventory/hosts
```
Edit it:
```
nano inventory/hosts
```
Configure your Matrix server and SSH user.
For example:
```
[matrix_servers]
matrix.example.com ansible_host=SERVER_IP ansible_user=YOUR_USERNAME
```
Use your actual server address and SSH username.
---
# 4\. Configure Matrix
Create the host variables directory:
```
mkdir -p inventory/host_vars/matrix.example.com
```
Copy the example variables:
```
cp examples/vars.yml inventory/host_vars/matrix.example.com/vars.yml
```
Edit the configuration:
```
nano inventory/host_vars/matrix.example.com/vars.yml
```
At minimum, configure your Matrix domain and the services you want to use.
Do not modify the Ansible roles themselves unless you have a specific reason to do so. Put custom configuration in your inventory variables.
---
# 5\. Configure DNS
You need DNS pointing your domain to the server.
A typical configuration might look like:
```
matrix.example.com A SERVER_IP
element.example.com A SERVER_IP
```
The exact DNS records depend on how you configure Matrix delegation.
There is an important distinction between the homeserver address and the Matrix user ID.
For example:
```
Homeserver:
matrix.example.com
Matrix ID:
@alice:example.com
```
These don't necessarily have to be the same hostname.
If you use delegation through `.well-known`, users can have the shorter:
```
@alice:example.com
```
while the actual Synapse server runs at:
```
matrix.example.com
```
This is worth deciding before creating users because changing the Matrix server name later is significantly more complicated.
---
# 6\. Firewall
Allow the ports required by your configuration.
A typical setup requires HTTP and HTTPS:
```
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
```
Matrix federation may also use:
```
sudo ufw allow 8448/tcp
```
If you use Coturn, allow the TURN ports and UDP relay range configured for your server.
Check for existing services using the required ports:
```
sudo ss -lntup
```
A reverse proxy such as Traefik cannot start if another service is already occupying its ports.
---
# 7\. Install the Ansible roles
Run:
```
just roles
```
This installs the Ansible roles required by the playbook.
---
# 8\. Install Matrix
The project provides convenient deployment commands.
For a complete initial deployment:
```
just install-all
```
You can also use:
```
just setup-all
```
depending on the operation you want to perform.
If your Ansible configuration requires a sudo password, use Ansible's become-password option when running the playbook directly:
```
ansible-playbook -i inventory/hosts setup.yml --ask-become-pass
```
After installation, check the containers:
```
docker ps
```
You should see Synapse, PostgreSQL, the reverse proxy and any other services enabled in your configuration.
---
# 9\. Create a Matrix user
The playbook provides a convenient command for creating users:
```
just register-user USERNAME PASSWORD yes
```
The third argument controls whether the user is an administrator.
Use:
```
yes
```
for an administrator:
```
just register-user alice 'PASSWORD' yes
```
or:
```
just register-user alice 'PASSWORD' no
```
for a regular user.
The command expects **`yes` or `no`**, not `1` or `0`.
The resulting Matrix ID depends on your server-name configuration. For example:
```
@alice:example.com
```
---
# 10\. Keep a second administrator account
It is strongly recommended to have at least two administrator accounts.
For example:
```
@alice:example.com
@admin-backup:example.com
```
The backup administrator should have a strong password and should not be used for normal day-to-day communication.
This provides a recovery mechanism if the primary administrator account is accidentally deactivated, deleted, or otherwise becomes inaccessible.
---
# 11\. Verify users
Because Synapse uses PostgreSQL, users can be inspected directly from the database.
For a typical Docker deployment:
```
docker exec matrix-postgres psql -U synapse -d synapse -c \
"SELECT name, admin, deactivated FROM users ORDER BY name;"
```
You might see:
```
name | admin | deactivated
-------------------------+-------+-------------
@alice:example.com | 1 | 0
@bob:example.com | 0 | 0
@olduser:example.com | 1 | 1
```
The relevant values are:
```
admin = 1
```
means the user is a Synapse administrator.
```
admin = 0
```
means the user is a regular user.
And:
```
deactivated = 0
```
means the account is active.
```
deactivated = 1
```
means the account is deactivated.
Direct database inspection is useful for troubleshooting, but administration should normally be performed through Synapse's supported administration mechanisms rather than modifying database rows manually.
---
# 12\. Synapse Admin
For easier administration, install a Synapse administration interface.
This provides a graphical interface for managing things such as:
- Users
- Administrator privileges
- Deactivated accounts
- Rooms
- Media
- Server settings
Log into the administration interface using an active Matrix administrator account.
A good practice is to keep a second administrator account specifically for recovery.
---
# 13\. Deactivating a user
Deactivation is useful when you want to prevent an account from being used without immediately destroying its historical data.
A deactivated account cannot log in normally.
However, deactivation does **not** mean that everything belonging to the user has been deleted.
Messages and media can remain on the server.
The distinction is therefore:
```
Deactivate
↓
Prevent account access
↓
Historical data remains
```
This is very different from permanently erasing data.
---
# 14\. Reactivating a user
A deactivated account can be reactivated by an administrator.
However, there is an important detail:
**Deactivation removes the user's password credential.**
Consequently, simply reactivating the account does not restore the old password.
When reactivating a deactivated user, set a new password through the supported administration interface.
Afterward, verify the account:
```
docker exec matrix-postgres psql -U synapse -d synapse -c \
"SELECT name, admin, deactivated FROM users WHERE name = '@alice:example.com';"
```
You want:
```
@alice:example.com | 1 | 0
```
if the user is supposed to be an active administrator.
---
# 15\. Deleting a user
Deleting and deactivating a user are different operations.
Deactivation is primarily an access-control operation.
Deletion or erasure is potentially destructive and may involve removing the user's account information and other data depending on the operation performed.
Before permanently deleting an account, consider:
- Messages in rooms
- Media uploaded by the user
- Membership in rooms
- Federation effects
- Backups
- Legal/data-retention requirements
For important accounts, take a backup before performing destructive operations.
Do not manually delete rows from the Synapse PostgreSQL database as a substitute for the supported user-management APIs.
---
# 16\. Matrix media storage
One of the largest directories on a Matrix server can be the media store.
In this Docker deployment it is commonly located under:
```
/matrix/synapse/storage/media-store
```
Check its size:
```
sudo du -sh /matrix/synapse/storage
```
For a more detailed breakdown:
```
sudo du -xhd2 /matrix/synapse/storage | sort -h
```
You may see directories such as:
```
local_content
local_thumbnails
remote_content
remote_thumbnail
url_cache
url_cache_thumbnails
```
Local media and cached remote media are stored separately.
---
# 17\. Quarantine is not deletion
This is an important distinction when using the Synapse administration interface.
If you quarantine media, Synapse prevents the media from being served normally.
It does **not necessarily mean the physical file has been removed from disk**.
Therefore:
```
Quarantine ≠ Delete
```
If you quarantine a large amount of media and immediately check:
```
du -sh /matrix/synapse/storage
```
you may see little or no reduction.
---
# 18\. Actually deleting Matrix media
Synapse provides administration APIs for deleting media.
The media administration API supports deleting specific media and deleting media according to criteria such as age.
This is preferable to manually deleting files from:
```
/matrix/synapse/storage/media-store
```
Do **not** simply run:
```
rm -rf /matrix/synapse/storage/media-store/*
```
The filesystem is part of Synapse's media-management system, and deleting files blindly can leave inconsistent state.
If you have used the Synapse Admin interface to mark media for deletion, allow the appropriate Synapse media-management process to perform the actual deletion, and verify the resulting disk usage afterward.
For large-scale cleanup, use the documented Synapse Admin media APIs or the administration interface rather than filesystem-level deletion.
---
# 19\. Finding what is actually using disk space
When the server starts running low on storage, don't assume Matrix media is responsible.
Start with:
```
df -h /
```
Then:
```
sudo du -xhd1 / | sort -h
```
If `/var` is large:
```
sudo du -xhd1 /var | sort -h
```
If `/matrix` is large:
```
sudo du -xhd1 /matrix | sort -h
```
This quickly tells you whether the problem is:
- Matrix media
- PostgreSQL
- Docker
- system logs
- package caches
- something else
---
# 20\. Docker disk usage
Docker can consume several gigabytes independently of Matrix.
Check:
```
docker system df
```
This shows:
- Images
- Containers
- Volumes
- Build cache
For example, a server might have a relatively small Matrix media store while Docker's build cache consumes several additional gigabytes.
You can inspect Docker's filesystem usage with:
```
sudo du -xhd1 /var/lib/docker | sort -h
```
The most important rule is:
**Do not manually delete files from `/var/lib/docker`.**
Use Docker's cleanup commands instead.
For example:
```
docker builder prune
```
removes unused build cache.
You can also inspect unused images:
```
docker image prune
```
For broader cleanup, Docker provides:
```
docker system prune
```
Read the command's output carefully before confirming. Never blindly prune volumes on a production Matrix server.
---
# 21\. PostgreSQL storage
PostgreSQL contains Synapse's database, but uploaded media is generally stored separately.
Check the PostgreSQL database size:
```
docker exec matrix-postgres psql -U synapse -d synapse -c \
"SELECT pg_size_pretty(pg_database_size('synapse'));"
```
Then compare it with the media store:
```
sudo du -sh /matrix/synapse/storage/media-store
```
This helps determine whether database maintenance or media cleanup is relevant.
---
# 22\. Backups
A Matrix backup should not consist of only a PostgreSQL dump.
At minimum, consider backing up:
```
PostgreSQL database
+
Synapse media store
+
Synapse configuration
+
Signing key
+
Other important service configuration
```
The signing key is particularly important. Losing it can cause serious problems for a homeserver's identity and federation.
Store backups somewhere other than the same server.
A backup sitting on the same disk as the production server is not a complete disaster-recovery strategy.
---
# 23\. Updating the installation
The playbook is actively maintained.
Before updating, review the project's documentation and changelog.
The `justfile` provides:
```
just update
```
and:
```
just update-playbook-only
```
The first can update the playbook and its pinned roles, while the second only updates the playbook itself.
After updating, check the running containers:
```
docker ps
```
and review logs if something doesn't start correctly:
```
docker logs matrix-synapse
```
---
# 24\. Useful troubleshooting commands
### Check running containers
```
docker ps
```
### Check Synapse logs
```
docker logs matrix-synapse --tail 100
```
### Check PostgreSQL
```
docker logs matrix-postgres --tail 100
```
### Check disk usage
```
df -h /
```
### Check Matrix storage
```
sudo du -sh /matrix/synapse/storage
```
### Find large directories
```
sudo du -xhd1 / | sort -h
```
### Check Docker usage
```
docker system df
```
### List Matrix users
```
docker exec matrix-postgres psql -U synapse -d synapse -c \
"SELECT name, admin, deactivated FROM users ORDER BY name;"
```
### Check one user
```
docker exec matrix-postgres psql -U synapse -d synapse -c \
"SELECT name, admin, deactivated FROM users WHERE name = '@alice:example.com';"
```
### Check available `just` commands
```
just --list
```
---
# 25\. Common mistakes
## Using `1` instead of `yes`
The playbook's user-registration helper expects:
```
just register-user USERNAME PASSWORD yes
```
or:
```
just register-user USERNAME PASSWORD no
```
not:
```
just register-user USERNAME PASSWORD 1
```
## Forgetting the admin argument
This is invalid:
```
just register-user alice password
```
because the recipe requires three arguments:
```
username
password
admin_yes_or_no
```
## Assuming the homeserver hostname is the Matrix ID domain
These can be different:
```
Homeserver:
matrix.example.com
User:
@alice:example.com
```
Always check your server-name and delegation configuration.
## Assuming deactivation deletes everything
It doesn't.
```
Deactivate ≠ Delete
```
## Assuming quarantine frees disk space
It doesn't necessarily.
```
Quarantine ≠ Delete
```
## Manually deleting media files
Avoid:
```
rm -rf /matrix/synapse/storage/media-store/*
```
Use Synapse's media administration mechanisms.
## Manually deleting Docker files
Avoid modifying:
```
/var/lib/docker
```
Use Docker's own pruning commands.
## Having only one administrator
Create a backup administrator before you need one.
---
# Conclusion
A self-hosted Matrix server is much easier to operate when the different components are understood separately.
The basic architecture is:
```
Internet
│
▼
Traefik / HTTPS
│
▼
Synapse
├── PostgreSQL
├── Media store
└── Other Matrix services
```
Ansible manages the configuration and deployment, while Docker runs the individual services.
The most important operational lessons are:
- Make sure Ansible can use `sudo`.
- Keep public registration disabled unless you need it.
- Keep at least two administrator accounts.
- Remember that the Matrix ID domain and homeserver hostname can be different.
- Deactivation prevents access but doesn't automatically erase historical data.
- Reactivating a deactivated account requires a new password.
- Deleting an account and deleting its media are separate concerns.
- Quarantining media does not necessarily reclaim disk space.
- Use Synapse's supported media administration tools instead of deleting media files manually.
- Check Docker disk usage before assuming Matrix is consuming all available storage.
- Never manually remove files from `/var/lib/docker`.
- Back up PostgreSQL, media, configuration, and signing keys.
- Keep the server and its dependencies updated.
For the exact commands and configuration options, always check the documentation shipped with the version of `matrix-docker-ansible-deploy` you are using, as both the playbook and Synapse continue to evolve.