Skip to content

Instantly share code, notes, and snippets.

@stancl
Created October 14, 2025 19:54
Show Gist options
  • Select an option

  • Save stancl/589c44ea4044ed7ffe9fba5f6823a453 to your computer and use it in GitHub Desktop.

Select an option

Save stancl/589c44ea4044ed7ffe9fba5f6823a453 to your computer and use it in GitHub Desktop.
Forgejo NixOS server
{
description = "System configuration";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};
outputs = { self, nixpkgs, ... }@inputs: {
nixosConfigurations = let
system = "aarch64-linux"; # Change to x86-64 if necessary
pkgs = nixpkgs.legacyPackages.${system};
lib = pkgs.lib;
in {
nixos = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix # This flake expects that you have the "original" system config in ./configuration.nix
{
nix.nixPath = [ "nixpkgs=${inputs.nixpkgs}" ];
security.acme.defaults.email = "<YOUR EMAIL>";
security.acme.acceptTerms = true;
}
({ config, ...}: {
services.forgejo = {
enable = true;
# See https://forgejo.org/docs/latest/admin/config-cheat-sheet
settings.server.DOMAIN = "<YOUR DOMAIN>";
settings.server.ROOT_URL = "https://<YOUR DOMAIN>";
settings.server.PROTOCOL = "http+unix";
settings.server.DISABLE_SSH = true;
settings.service.DISABLE_REGISTRATION = true; # Set this to false, register your account, then set this to true.
settings.ui.DEFAULT_THEME = "gitea-auto";
settings.DEFAULT.APP_NAME = "<YOUR SITE NAME>";
settings.api.ENABLE_SWAGGER = false;
settings.database.SQLITE_TIMEOUT = 1000;
settings.database.SQLITE_JOURNAL_MODE = "WAL";
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx.enable = true;
services.nginx.virtualHosts."<YOUR DOMAIN>" = {
forceSSL = true;
enableACME = true;
# The default home page is pointless without registration
# so we just redirect to the repo list.
locations."= /".extraConfig = ''
return 302 /explore/repos;
'';
# HTTP addr contains the unix socket path when server.PROTOCOL = "http+unix";
locations."/".proxyPass = "http://unix:${config.services.forgejo.settings.server.HTTP_ADDR}:";
# Global nginx extra config - use Cloudflare Authenticated Origin Pulls
# (= easy way to block non-CF traffic)
# See https://github.com/archtechx/nix#authenticated-origin-pulls-aop
extraConfig = ''
ssl_verify_client on;
ssl_client_certificate ${pkgs.fetchurl {
url = "https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem";
sha256 = "0hxqszqfzsbmgksfm6k0gp0hsx9k1gqx24gakxqv0391wl6fsky1";
}};
'';
};
})
{
# Override the user's IP with CF-Connecting-IP when the request comes from
# one of the IP addresses in this list.
# This list barely updates but it's okay if it becomes outdated, at worst some users
# will be shown as connecting from a CF IP instead of their real IP. The IP list becoming
# outdated would be a bigger issue with blocking non-CF traffic, hence why we use AOP
# instead of an IP list above.
# If this list were to become outdated when you're building this config, the checksum would catch that.
services.nginx.commonHttpConfig =
let
realIpsFromList = lib.strings.concatMapStringsSep "\n" (x: "set_real_ip_from ${x};");
fileToList = x: lib.strings.splitString "\n" (builtins.readFile x);
cfipv4 = fileToList (pkgs.fetchurl {
url = "https://www.cloudflare.com/ips-v4";
sha256 = "0ywy9sg7spafi3gm9q5wb59lbiq0swvf0q3iazl0maq1pj1nsb7h";
});
cfipv6 = fileToList (pkgs.fetchurl {
url = "https://www.cloudflare.com/ips-v6";
sha256 = "1ad09hijignj6zlqvdjxv7rjj8567z357zfavv201b9vx3ikk7cy";
});
in
''
${realIpsFromList cfipv4}
${realIpsFromList cfipv6}
real_ip_header CF-Connecting-IP;
'';
}
];
};
};
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment