Selfhosting a Forgejo instance on NixOS

on: 2026-04-18

I got bored and felt like setting up a Forgejo instance on my VPS. I saw that forgejo is currently developing federation. Federation means that you could collaborate across forgejo instances without having to create multiple accounts. That seemed cool enough and so, with my basic Nix skills I went ahead and got started :)

Contents

Basic Forgejo Configuration

I wanted to set it up with behind a nginx reverse proxy at https://git.cibob.net/. Setting up the forgejo service was relatively straight forward, I looked at the options available on nixpkgs and at the forgejo documentation (and some random github issue to figure out some misconfiguration I had …).

let
  domain = "git.cibob.net";
in
{
  services.forgejo = {
    enable = true;

    settings = {
      server = {
        DOMAIN = "${domain}";
        HTTP_PORT = 3001; # the port we want forgejo to listen to
        PROTOCOL = "http";
        ROOT_URL = "https://${domain}/";
      };
      # disable registration while setting up administrators
      service.DISABLE_REGISTRATION = true; 
    };

    # setting up the default simple sqlite database.
    # (the createDatabase option is enabled by default)
    database = {
      type = "sqlite3";
    };
  };
# ...

The main issue I ran into was when when I blindly tried to set STATIC_ROOT_PATH, which caused a bunch of strange locale issues link to issue. After removing that it worked fine.

NGINX reverse proxy

To set Forgejo up under a subpath, I would check out: http with a subpath. For my set-up, I am using a subdomain.

Forgejo says in their configuration guide to set the extra configuration:

proxy_set_header Connection $http_connection;
proxy_set_header Upgrade $http_upgrade;
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;

but since I have services.nginx.recommendedProxySettings = true; set already, I set the extraConfig:

 services.nginx = {
   virtualHosts."${domain}" = {
     useACMEHost = "cibob.net";
     forceSSL = true;

     locations."/" = {
       proxyPass = "http://localhost:3001";
       extraConfig = ''
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection $http_connection;

         client_max_body_size 512M;
       '';
     };
   };
 };

After a nixos-rebuild switch, I had a running forgejo instance.

Setting up users declaritively

From the NixOS wiki I found the following snippet, which sets up admins and users (if you leave out the —admin flag). This was all I needed to set up a basic selfhosted forge.

sops.secrets.forgejo-admin-password.owner = "forgejo";
systemd.services.forgejo.preStart = let 
  adminCmd = "${lib.getExe cfg.package} admin user";
  pwd = config.sops.secrets.forgejo-admin-password;
  user = "joe"; # Note, Forgejo doesn't allow creation of an account named "admin"
in ''
  ${adminCmd} create --admin --email "root@localhost" --username ${user} --password "$(tr -d '\n' < ${pwd.path})" || true
  ## uncomment this line to change an admin user which was already created
  # ${adminCmd} change-password --username ${user} --password "$(tr -d '\n' < ${pwd.path})" || true
'';

All the forgejo data gets stored under var/lib/forgejo/... We can see the forgejo.db is owned by forgejo:forgejo. And this is secure enough for me for now. Later on I will change it to PostgreSQL and set up an according secret.

root@cupos /var/lib/forgejo# ll data/
total 2.3M
drwxr-x--- 1 forgejo forgejo  230 Apr 18 18:25 .
drwxr-x--- 1 forgejo forgejo   74 Apr 18 01:15 ..
drwxr-x--- 1 forgejo forgejo    0 Apr 18 01:15 actions_artifacts
drwxr-x--- 1 forgejo forgejo    0 Apr 18 01:15 actions_log
drwxr-x--- 1 forgejo forgejo    0 Apr 18 01:15 attachments
drwxr-x--- 1 forgejo forgejo   70 Apr 18 03:40 avatars
-rw-r----- 1 forgejo forgejo 2.2M Apr 18 18:25 forgejo.db
drwxr-x--- 1 forgejo forgejo   20 Apr 18 01:15 home
drwx------ 1 forgejo forgejo   24 Apr 18 01:15 indexers
drwxr-x--- 1 forgejo forgejo   22 Apr 18 01:15 jwt
drwxr-x--- 1 forgejo forgejo    0 Apr 18 01:15 packages
drwxr-x--- 1 forgejo forgejo   12 Apr 18 01:15 queues
drwxr-x--- 1 forgejo forgejo    0 Apr 18 01:15 repo-archive
drwxr-x--- 1 forgejo forgejo    0 Apr 18 01:15 repo-avatars
drwxr-x--- 1 forgejo forgejo   44 Apr 18 04:13 ssh
drwxr-x--- 1 forgejo forgejo   28 Apr 18 01:15 tmp

Extra Setup

SSH

To set up the built-in SSH server:

services.forgejo.server = {
  ...
  START_SSH_SERVER = true; # use the builtin SSH server
  SSH_PORT = 2222; # port displayed in clone URL
  SSH_LISTEN_PORT = 2222; # Port for the built-in SSH server
};

I will change this in the future to use the local SSH server.

Woodpecker (CI/CD)

Instead of using the built-in forgejo runner, I wanted to integrate it with my existing woodpecker instance. Just to get everything up and running, in the future, I would set up gitea/forgejo.

I changed the config of woodpecker-server:

services.woodpecker-server.environment =  {
  ...
  WOODPECKER_FORGEJO = "true";
  WOODPECKER_FORGEJO_URL = "https://git.cibob.net";
  WOODPECKER_FORGEJO_CLIENT = "client-id";
  WOODPECKER_FORGEJO_SECRET_FILE = config.sops.secrets.woodpecker_client_secret.path;
};

The client token and secret you can generate in the forgejo instance under settings -> Applications -> Manage OAuth2 applications.

Anubis

To try and block bots and scrapers, I used anubis:

let
internalPort = 3001;
anubisPort = 3002;
in
...
services.anubis = {
  instances."forgejo" = {
    enable = true;
    settings = {
      TARGET = "http://localhost:${toString internalPort}";
      BIND_NETWORK = "tcp";
      BIND = ":${toString anubisPort}";
    };
  };
};
# changed virtual host proxy pass to point to the anubis port
proxyPass = "http://localhost:${toString anubisPort}";

# changed the forgejo server HTTP port
HTTP_PORT = internalPort;

Here the anubis instance uses settings defined in defaultOptions.

Conclusion

Overall, the setup process was very painless, thanks to the available NixOS modules. Whenever I was stuck, I found a solution either by reading the docs, on some git issue or the nixos module. This was the first blog post I’ve ever written, I’m not at all an expert on the stuff I am writing about. But maybe this will help someone set up something similar :)

To see the forgejo configuration with flake-parts, see forgejo.nix. And my full NixOS configuration is at: git.cibob.net/cibob/nixconfig.