Getting Started
Installing Hammerhead is relatively simple, but you need to be confident with managing a server to get the most out of it. This guide will walk you through the minimum steps required to get a working installation of Hammerhead.
This part of the guide is very in depth - consider using the TOC in the sidebar to help you navigate.
Prerequisites
To compile the server (for when you aren’t using a pre-built binary), you will need:
- The latest version of Go.
- A compatible Linux operating system: Debian-based, Arch, Fedora. Others may work but are not tested.
gitandbashin your$PATH.gccis NOT currently required.
To run the server, you will need:
- PostgreSQL v14 or newer (check for issues regarding newly released PG versions).
To run the server, you will also want:
Important
Once you have set your “server name”, it cannot be changed. This means, if you start off without a domain, and later want to switch to using one, you will have to delete your database and start fresh.
Compiling
If you wish to compile Hammerhead (instead of using the binaries created by CI or attached to releases), you can do so
with the handy build.sh script located at the root of this repository. While using this script in particular is not
required (compiling with plain go build is sufficient), the build script conveniently injects build metadata into the
binary for accurate version reporting, and offers shorthands to compile static and release-optimised builds.
Note
You do not need to compile Hammerhead to run it. CI produces static binaries for AMD64 and ARM64 on each push to
dev, and static binaries are also attached to each release.Unless you need a dynamic binary, or are planning on hacking on Hammerhead, you likely do not need to compile.
Please skip to Installing if you just want to install Hammerhead.
With the build script
To get started, make sure you have the prerequisites detailed above. Then, you can clone the repository:
git clone https://codeberg.org/timedout/hammerhead.git && cd hammerhead
And then run the build script to produce a binary at ./bin/hammerhead:
./build.sh
#- will build dynamically-linked binary
#- will build debug binary (without optimizations)
#- Tag associated with the latest commit: N/A
#- Latest tag: N/A
#- Latest commit hash: ec0f1d0
#- Dirty? yes
#- Build date: 2026.03.09T19.48.00Z
#- Golang version: go1.26.0
#- OS/Arch: linux/amd64
#
#Compiling hammerhead
#...
#codeberg.org/timedout/hammerhead/cmd/hammerhead
#
#real 0m1.941s
#user 0m1.779s
#sys 0m0.785s
Notice how the first two lines say will build dynamically-linked binary and
will build debug binary (without optimizations)? This is because by default, the script will create a debug-oriented
build. While this is still plenty fast, it is dynamically linked, and retains debug symbols, meaning it’s a few
mebibytes larger than necessary for most people. If you aren’t planning on running through Hammerhead with a debugger,
you may wish to instead build a high-performance and/or static binary.
To compile a static binary, pass -static to build.sh:
./build.sh -static && file ./bin/hammerhead
#- will build static-linked binary without CGO (experimental!)
#- will build debug binary (without optimizations)
# ...
#./bin/hammerhead: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=..., BuildID[sha1]=..., with debug_info, not stripped
To compile a “release” binary (one without debug symbols), pass -release:
./build.sh -release && file ./bin/hammerhead
#- will build dynamically-linked binary
#- will build release binary (with optimizations)
#...
#./bin/hammerhead: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=..., BuildID[sha1]=..., stripped
You can even combine these flags (in any order) to compile a release+static binary:
./build.sh -static -release && file ./bin/hammerhead
#- will build static-linked binary without CGO (experimental!)
#- will build release binary (with optimizations)
#./bin/hammerhead: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=..., BuildID[sha1]=..., stripped
Without the build script
If for some reason you are unable to use the build script (consider opening an issue!), you can still compile without:
go build -o ./bin/ ./cmd/hammerhead
Note that this will produce a debug dynamic binary without any metadata. You will likely be unable to report bugs found when running this binary as it will not contain version data required to accurately troubleshoot problems.
Installing
In order to install Hammerhead, you will first need a binary. These can be acquired from:
Please note that CI artifacts are zipped when downloaded - you will first need to unzip them before you can get the
binary within.
Make sure you select the correct binary by checking the output of uname -m:
| Output | Version |
|---|---|
x86_64 | AMD64 |
aarch64 | ARM64 |
Other architectures may work, but are not built in CI, nor tested. Downloading the wrong binary will result in
an error message like exec format error: ./hammerhead-arm64 when you try to run it.
Configuring
Important
The rest of this guide assumes your binary (from compiling or installing) is located at
/usr/local/bin/hammerhead, and that/usr/local/binis in your$PATH. It also assumes you have read+write+execute access to/etc/hammerhead.
Before you daemonise Hammerhead, you’re going to want to create the template configuration file. When Hammerhead is unable to load a configuration file, it will create one with some default values, so we’re going to use this to create a sparse config file that you can modify with your values.
First, create the configuration file by running hammerhead -config /etc/hammerhead/config.yaml. Upon success,
this will create a YAML file at that location with some example values. You must edit at least:
server_name- set this to your server name (the part after the:in your user IDs).database.url- set this to the connection URI of your postgres server. Must contain the username, password, host, and database name for the connection.
Caution
After you start the server for the first time the
server_namecannot be changed. Make sure you’re certain before you hit run!
You may also wish to modify the other options, such as the registration.token (to change it to one of your
choosing), changing the password requirements, increasing the max_request_bytes (to allow larger file uploads),
or modifying the listeners.
A configuration reference does not yet exist but will be added at a later date.
Importing a previous installation’s signing key
It is possible to import another installation’s server signing key, provided you have it in a file with the format of
ed25519 KeyID PrivateKeyPart, for example: ed25519 a_bcde hnBjtF9w9AQAWfnhAHIV3fdu9QH0YX1xWlb0qEPjE4w
You can then import this key via hammerhead -import-signing-key path/to/file. Once you confirm you want to import it,
it will be imported as an “expired” key, meaning it can no longer be used to sign new events. It can, however, still be
used to verify events sent from before you set up Hammerhead.
Tip
If, for some reason, you wish to continue using the imported key to sign new events, you will need to manually do so via postgres:
UPDATE owned_signing_keys SET expired_ts = NULL;.While technically supported, you should not have multiple active signing keys - if Hammerhead automatically generated one, you should invalidate it before continuing with
hammerhead -invalidate-signing-key 'KEY_ID'. Again, once a key is expired, it cannot be used ever again.
Exporting your current key
If you wish to migrate your deployment to another homeserver implementation, you can export your active signing key into
the synapse format with hammerhead -export-signing-key. This does not expire the key, so it can be re-used as an
active key in another deployment (as long as it has the same server name).
Starting the server
To start Hammerhead after configuring it, you can just run the binary like you did the first time:
hammerhead -config /etc/hammerhead/config.yaml
#=== Hammerhead v0.0.1-dev ===
#Parsing command line flags...
#Loading configuration from /etc/hammerhead/config.yaml...
#Initialising logging...
#Dropping output into logging system...
#2026-03-09T19:84:00Z DBG Validating configuration
#2026-03-09T19:84:00Z INF Initialising server
#2026-03-09T19:84:00Z INF running database migrations
#2026-03-09T19:84:00Z INF finished running database migrations elapsed_ms=181
#2026-03-09T19:84:00Z WRN no signing key found, generating new one. If this was not expected, I hope you have a backup of the one you expected.
#2026-03-09T19:84:00Z INF generated new signing key key_id=ed25519:vgJGqQ
#2026-03-09T19:84:00Z INF loaded signing key key_id=ed25519:vgJGqQ
#2026-03-09T19:84:00Z INF initialising media repository
#2026-03-09T19:84:00Z INF media repository initialised elapsed_ms=2
#2026-03-09T19:84:00Z INF Passing off server startup to server instance
#2026-03-09T19:84:00Z INF Starting server on
#2026-03-09T19:84:00Z INF server started. ^C to shut down.
#2026-03-09T19:84:00Z INF starting listener address=:8008 tls=false
If your configuration is malformed in such a way that Hammerhead would not be able to operate, starting the server will fail, and it will tell you what you need to change.
As soon as you see server started. and starting listener, your server is ready to go! Keep it running like this
for the next step.
Creating your first user
After you’ve started the server, there will be no users. Open a client such as Element Web, Cinny, Sable, or Commet, and plug in your server name. From there, go to “register”, and put in the username and password you desire.
You will likely be challenged to supply the token that is under the registration section of your configuration.
This is to prevent automated scripts or abusive users from finding your server, and registering accounts without your
supervision.
Upon registering the first user, you will be granted admin, which allows you to control things that happen on your deployment via the admin API. TODO: Admin API reference.
Any users registered after the first will simply be standard users who have no special control or rights on the server.
Tip
You can mark anyone as an admin by updating their admin status via postgres:
UPDATE accounts SET admin = true WHERE localpart = 'username_here';(NOTE: username, not user ID)
Daemonising
In order to run Hammerhead 24/7, you will want to first interrupt the running server by hitting CTRL+C (running concurrent instances of Hammerhead is not safe), and create a systemd unit file like below:
# /etc/systemd/system/hammerhead.service
[Unit]
Description=Hammerhead Matrix homeserver
Documentation=https://timedout.codeberg.page/hammerhead
Requires=network.target
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=hammerhead
Group=hammerhead
ExecStart=/usr/local/bin/hammerhead
WorkingDirectory=/etc/hammerhead
Environment=HAMMERHEAD_CONFIG=/etc/hammerhead/config.yaml
Restart=on-failure
RestartSec=2
RestartSteps=5
RestartMaxDelaySec=1m
StartLimitIntervalSec=1m
StartLimitBurst=5
[Install]
WantedBy=multi-user.target
You will also need to create a user and group for Hammerhead to use this systemd unit, which can be achieved on most
Linux distributions like so: adduser --system --group --home /etc/hammerhead hammerhead. Make sure you
chown -R hammerhead:hammerhead /etc/hammerhead and chmod 755 /usr/local/bin/hammerhead so that Hammerhead can
read & write in its site directory, and execute the binary.
You can then systemctl daemon-reload to init the unit file, and then use systemctl enable --now hammerhead.service
to both enable the service at startup, and also start it immediately. Once start returns, you can check the status
and most recent log lines of Hammerhead by running systemctl status hammerhead.service:
systemctl status hammerhead.service
#● hammerhead.service - hammerhead
# Loaded: loaded (/etc/systemd/system/hammerhead.service; enabled; preset: enabled)
# Active: active (running) since Mon 2026-03-09 19:84:00 GMT; 0s ago
# Main PID: 129208 (hammerhead)
# Tasks: 9 (limit: 57189)
# Memory: 8.6M (peak: 10.6M)
# CPU: 23ms
# CGroup: /system.slice/hammerhead.service
# └─129208 /usr/local/bin/hammerhead
#
#Mar 09 19:84:00 hammerhead-staging hammerhead[129208]: 2026-03-09T19:84:0Z INF Initialising server
#Mar 09 19:84:00 hammerhead-staging hammerhead[129208]: 2026-03-09T19:84:0Z INF running database migrations
#Mar 09 19:84:00 hammerhead-staging hammerhead[129208]: 2026-03-09T19:84:0Z INF finished running database migrations elapsed_ms=31
#Mar 09 19:84:00 hammerhead-staging hammerhead[129208]: 2026-03-09T19:84:0Z INF loaded signing key key_id=ed25519:lZ2Vwg
#Mar 09 19:84:00 hammerhead-staging hammerhead[129208]: 2026-03-09T19:84:0Z INF initialising media repository
#Mar 09 19:84:00 hammerhead-staging hammerhead[129208]: 2026-03-09T19:84:0Z INF media repository initialised elapsed_ms=3
#Mar 09 19:84:00 hammerhead-staging hammerhead[129208]: 2026-03-09T19:84:0Z INF Passing off server startup to server instance
#Mar 09 19:84:00 hammerhead-staging hammerhead[129208]: 2026-03-09T19:84:0Z INF Starting server on
#Mar 09 19:84:00 hammerhead-staging hammerhead[129208]: 2026-03-09T19:84:0Z INF server started. ^C to shut down.
#Mar 09 19:84:00 hammerhead-staging hammerhead[129208]: 2026-03-09T19:84:0Z INF starting listener address=0.0.0.0:8008 tls=false