---
title: Self-hosting a knot
description: "Run your own knot: a lightweight, headless git server."
---


So you want to run your own knot server? Great! Here are a few prerequisites:

1. A server of some kind (a VPS, a Raspberry Pi, etc.). Preferably running a Linux distribution of some kind.
2. A (sub)domain name. People generally use `knot.example.com`.
3. A valid SSL certificate for your domain.

## NixOS

Refer to the [knot
module](https://tangled.org/tangled.org/core/blob/master/nix/modules/knot.nix)
for a full list of options. Sample configurations:

- [The test VM](https://tangled.org/tangled.org/core/blob/master/nix/vm.nix#L85)
- [@pyrox.dev/nix](https://tangled.org/pyrox.dev/nix/blob/c2b644c214d278af12523618de952ee2eab1af3d/hosts/marvin/services/tangled.nix#L15-26)

## Docker

Refer to
[@tangled.org/knot-docker](https://tangled.org/@tangled.org/knot-docker).
Note that this is community maintained.

## Manual setup

First, clone this repository:

```
git clone https://tangled.org/@tangled.org/core
```

Then, build the `knot` CLI. This is the knot administration
and operation tool. For the purpose of this guide, we're
only concerned with these subcommands:

- `knot server`: the main knot server process, typically
  run as a supervised service
- `knot guard`: handles role-based access control for git
  over SSH (you'll never have to run this yourself)
- `knot keys`: fetches SSH keys associated with your knot;
  we'll use this to generate the SSH
  `AuthorizedKeysCommand`

```
cd core
export CGO_ENABLED=1
go build -o knot ./cmd/knot
```

Next, move the `knot` binary to a location owned by `root` --
`/usr/local/bin/` is a good choice. Make sure the binary itself is also owned by `root`:

```
sudo mv knot /usr/local/bin/knot
sudo chown root:root /usr/local/bin/knot
```

This is necessary because SSH `AuthorizedKeysCommand` requires [really
specific permissions](https://stackoverflow.com/a/27638306). The
`AuthorizedKeysCommand` specifies a command that is run by `sshd` to
retrieve a user's public SSH keys dynamically for authentication. Let's
set that up.

```
sudo tee /etc/ssh/sshd_config.d/authorized_keys_command.conf <<EOF
Match User git
  AuthorizedKeysCommand /usr/local/bin/knot keys -o authorized-keys
  AuthorizedKeysCommandUser nobody
EOF
```

Then, reload `sshd`:

```
sudo systemctl reload ssh
```

Next, create the `git` user. We'll use the `git` user's home directory
to store repositories:

```
sudo adduser git
```

Create `/home/git/.knot.env` with the following, updating the values as
necessary. The `KNOT_SERVER_OWNER` should be set to your
DID, you can find your DID in the [Settings](https://tangled.sh/settings) page.

```
KNOT_REPO_SCAN_PATH=/home/git
KNOT_SERVER_HOSTNAME=knot.example.com
APPVIEW_ENDPOINT=https://tangled.org
KNOT_SERVER_OWNER=did:plc:foobar
KNOT_SERVER_INTERNAL_LISTEN_ADDR=127.0.0.1:5444
KNOT_SERVER_LISTEN_ADDR=127.0.0.1:5555
```

If you run a Linux distribution that uses systemd, you can
use the provided service file to run the server. Copy
[`knotserver.service`](https://tangled.org/tangled.org/core/blob/master/systemd/knotserver.service)
to `/etc/systemd/system/`. Then, run:

```
systemctl enable knotserver
systemctl start knotserver
```

The last step is to configure a reverse proxy like Nginx or Caddy to front your
knot. Here's an example configuration for Nginx:

```
server {
    listen 80;
    listen [::]:80;
    server_name knot.example.com;

    location / {
        proxy_pass http://localhost:5555;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # wss endpoint for git events
    location /events {
        proxy_set_header   X-Forwarded-For $remote_addr;
        proxy_set_header   Host $http_host;
        proxy_set_header Upgrade websocket;
        proxy_set_header Connection Upgrade;
        proxy_pass http://localhost:5555;
    }
  # additional config for SSL/TLS go here.
}

```

Remember to use Let's Encrypt or similar to procure a certificate for your
knot domain.

You should now have a running knot server! You can finalize
your registration by hitting the `verify` button on the
[/settings/knots](https://tangled.org/settings/knots) page. This simply creates
a record on your PDS to announce the existence of the knot.

### Custom paths

(This section applies to manual setup only. Docker users should edit the mounts
in `docker-compose.yml` instead.)

Right now, the database and repositories of your knot lives in `/home/git`. You
can move these paths if you'd like to store them in another folder. Be careful
when adjusting these paths:

- Stop your knot when moving data (e.g. `systemctl stop knotserver`) to prevent
  any possible side effects. Remember to restart it once you're done.
- Make backups before moving in case something goes wrong.
- Make sure the `git` user can read and write from the new paths.

#### Database

As an example, let's say the current database is at `/home/git/knotserver.db`,
and we want to move it to `/home/git/database/knotserver.db`.

Copy the current database to the new location. Make sure to copy the `.db-shm`
and `.db-wal` files if they exist.

```
mkdir /home/git/database
cp /home/git/knotserver.db* /home/git/database
```

In the environment (e.g. `/home/git/.knot.env`), set `KNOT_SERVER_DB_PATH` to
the new file path (_not_ the directory):

```
KNOT_SERVER_DB_PATH=/home/git/database/knotserver.db
```

#### Repositories

As an example, let's say the repositories are currently in `/home/git`, and we
want to move them into `/home/git/repositories`.

Create the new folder, then move the existing repositories (if there are any):

```
mkdir /home/git/repositories
# move all DIDs into the new folder; these will vary for you!
mv /home/git/did:plc:wshs7t2adsemcrrd4snkeqli /home/git/repositories
```

In the environment (e.g. `/home/git/.knot.env`), update `KNOT_REPO_SCAN_PATH`
to the new directory:

```
KNOT_REPO_SCAN_PATH=/home/git/repositories
```

Similarly, update your `sshd` `AuthorizedKeysCommand` to use the updated
repository path:

```
sudo tee /etc/ssh/sshd_config.d/authorized_keys_command.conf <<EOF
Match User git
  AuthorizedKeysCommand /usr/local/bin/knot keys -o authorized-keys -git-dir /home/git/repositories
  AuthorizedKeysCommandUser nobody
EOF
```

Make sure to restart your SSH server!

#### MOTD (message of the day)

To configure the MOTD used ("Welcome to this knot!" by default), edit the
`/home/git/motd` file:

```
printf "Hi from this knot!\n" > /home/git/motd
```

Note that you should add a newline at the end if setting a non-empty message
since the knot won't do this for you.

## Secure Mode

Secure Mode isolates each `git` subprocess to the repository it is
operating on, using two mechanisms:

- **Linux Landlock** restricts the filesystem paths the subprocess
  can access -- it can only read/write its own repository and the
  system directories it needs to run.
- **UID isolation** runs each subprocess as a virtual UID assigned
  to the repository owner, so that repositories belonging to
  different owners are isolated from each other at the OS level
  even if Landlock were somehow bypassed.

Secure Mode requires:

- Linux kernel >= 5.19 (Landlock V2). This is the minimum needed
  for `git push` to work, because receive-pack's quarantine
  migration uses cross-directory rename which requires the
  Landlock `REFER` access right (added in V2). Kernels 5.13-5.18
  support Landlock V1 and clones will work, but pushes will fail
  with cross-device link errors. On kernels without any Landlock
  support (< 5.13), the sandbox call is a no-op: UID isolation
  still applies but no filesystem restriction is enforced.
- `CAP_SETUID`, `CAP_SETGID`, and `CAP_CHOWN` available to the
  knot process. The NixOS module grants these automatically; for
  manual setups see the `setcap` step below.

### NixOS

Add `server.secureMode = true;` to your knot module configuration:

```nix
services.tangled.knot = {
  server.secureMode = true;
  # ... other options
};
```

The NixOS module handles everything else automatically:

- Grants the required capabilities to the knot service via
  `AmbientCapabilities` in the systemd unit.
- Installs a capability-bearing wrapper at
  `/run/wrappers/bin/knot` via `security.wrappers`, so that
  SSH-invoked git operations (pushes) also run under the correct
  UID without requiring the service to run as root.
- Runs `knot migrate-isolation` at service start to chown
  existing repositories to their virtual UIDs.

### Manual setup

**Step 1.** Grant the required capabilities to the knot binary.
This allows the knot process to switch to virtual UIDs at runtime
without running as root. You will need to repeat this step
whenever the binary is updated.

```
sudo setcap cap_setuid,cap_setgid,cap_chown+eip /usr/local/bin/knot
```

**Step 2.** Run the migration tool to assign virtual UIDs to all
existing repositories and set their filesystem permissions. This
must be run as root:

```
sudo knot migrate-isolation \
  --git-dir /home/git \
  --db /home/git/knotserver.db \
  --internal-api 127.0.0.1:5444
```

You can re-run this at any time with `--force` to reapply
permissions (e.g. after a manual repair or after updating the
binary).

**Step 2a.** Ensure the home directory is traversable by
non-group users. Git subprocesses run as virtual UIDs that are
not in the git group, and they need to resolve
`$HOME/.config/git/config` to load the global config:

```
sudo chmod o+x /home/git
```

This adds only the execute bit, not read -- the virtual UIDs can
traverse to known paths but cannot list directory contents.

**Step 3.** Enable Secure Mode in your environment file:

```
KNOT_SERVER_SECURE_MODE=true
```

Or pass it as a flag:

```
knot server --secure-mode
```

**Step 4.** Regenerate the `AuthorizedKeysCommand` with the
`-secure-mode` flag. This causes `knot keys` to emit guard
command lines that include `-secure-mode`, so SSH pushes also
get UID isolation:

```
sudo tee /etc/ssh/sshd_config.d/authorized_keys_command.conf <<EOF
Match User git
  AuthorizedKeysCommand /usr/local/bin/knot keys \
    -o authorized-keys -secure-mode
  AuthorizedKeysCommandUser nobody
EOF
```

Reload `sshd` after making this change.

> **Note:** the server will refuse to start in Secure Mode if any
> repositories have not yet been isolation-migrated. Re-run
> `migrate-isolation` if you see this error.

## Troubleshooting

If you run your own knot, you may run into some of these
common issues. You can always join the
[IRC](https://web.libera.chat/#tangled) or
[Discord](https://chat.tangled.org/) if this section does
not help.

### Unable to push

If you are unable to push to your knot or repository:

1. First, ensure that you have added your SSH public key to
   your account
2. Check to see that your knot has synced the key by running
   `knot keys`
3. Check to see if git is supplying the correct private key
   when pushing: `GIT_SSH_COMMAND="ssh -v" git push ...`
4. Check to see if `sshd` on the knot is rejecting the push
   for some reason: `journalctl -xeu ssh` (or `sshd`,
   depending on your machine). These logs are unavailable if
   using docker.
5. Check to see if the knot itself is rejecting the push,
   depending on your setup, the logs might be in one of the
   following paths:
   - `/tmp/knotguard.log`
   - `/home/git/log`
   - `/home/git/guard.log`
