{"id":30304511,"url":"https://github.com/archtechx/nix","last_synced_at":"2026-05-17T19:35:58.986Z","repository":{"id":309642259,"uuid":"1024581369","full_name":"archtechx/nix","owner":"archtechx","description":"A collection of scripts and config files, mainly for deploying Laravel on NixOS","archived":false,"fork":false,"pushed_at":"2025-08-28T16:15:44.000Z","size":78,"stargazers_count":9,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-28T22:43:49.932Z","etag":null,"topics":["laravel","nix","nixos"],"latest_commit_sha":null,"homepage":"https://stancl.substack.com/p/deploying-laravel-on-nixos","language":"Nix","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/archtechx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-22T23:57:55.000Z","updated_at":"2025-08-28T16:15:48.000Z","dependencies_parsed_at":"2025-08-13T03:10:54.028Z","dependency_job_id":"5ab228cf-f7b8-4b59-9bc7-a5f492d82458","html_url":"https://github.com/archtechx/nix","commit_stats":null,"previous_names":["archtechx/nix"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/archtechx/nix","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archtechx%2Fnix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archtechx%2Fnix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archtechx%2Fnix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archtechx%2Fnix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/archtechx","download_url":"https://codeload.github.com/archtechx/nix/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archtechx%2Fnix/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33151930,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T09:28:26.183Z","status":"ssl_error","status_checked_at":"2026-05-17T09:27:52.702Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["laravel","nix","nixos"],"created_at":"2025-08-17T07:09:07.531Z","updated_at":"2026-05-17T19:35:58.980Z","avatar_url":"https://github.com/archtechx.png","language":"Nix","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nix scripts\n\nA collection of scripts and configuration files for our use of Nix tooling.\n\n\u003e [!NOTE]\n\u003e You may want to read [**this article**](https://stancl.substack.com/p/deploying-laravel-on-nixos) for more detailed information.\n\n## Setting up a new server\n\nThis is just for getting a working NixOS installation with `/etc/nixos/configuration.nix` deployed onto a generic cloud VM.\n\nThe setup also uses `/etc/nixos/flake.nix` since that's an easy way of addressing\n[the nixos-anywhere NIX_PATH issue](https://nix-community.github.io/nixos-anywhere/howtos/nix-path.html)\nand you likely want to use flakes anyway.\n\n**Note: All of the automated scripts for the steps below assume you're logging in as root**. If that's not the case, just follow\nthe steps manually. The scripts will also create lockfiles in `anywhere/` and `postinstall/` to make future deployments consistent\nand faster (by reusing more things from your nix store). Feel free to delete those if you want a completely fresh install each time.\n\nThis section is overall just a thin wrapper around nixos-anywhere.\n\n### Installing NixOS\n\n- Provision a new server. This config works on Hetzner Cloud, may require adjustments for other\n  providers, see anywhere/flake.nix\n    - The default config uses `aarch64`, you can change this to `x86_64`\n- Preferably use passwordless auth with just your SSH key\n\n\u003e Cross-compilation is sometimes buggy so it's recommended to run this on Linux (use a NixOS VM if you're on macOS), preferably\n\u003e matching the server's ISA. On macOS I highly recommend creating a NixOS VM (helpful for development anyway) in Parallels with\n\u003e no desktop environment, ssh enabled, and shared folders.\n\u003e\n\u003e That said, running this on macOS *should* still work fine, again ideally on the same ISA as the server (hence the aarch64 default).\n\nNow either run `(cd anywhere \u0026\u0026 ./auto.sh \u003cserver_ip\u003e \u003cpath_to_your_ssh_key\u003e)`, with the path being e.g. `~/.ssh/id_ed25519.pub`. Or\nif you want to do this manually (or make customizations):\n- **Put the key into anywhere/configuration.nix (the REPLACEME) so you can log in after NixOS is installed**\n- Run `nix run nixpkgs#nixos-anywhere -- --flake .#cloud root@\u003cyour-server-ip\u003e`\n    - Replace the output name if you've changed it\n    - The user doesn't have to be root but has to be able to `sudo` without entering a password\n    - You need Nix installed with the `nix-command` experimental feature enabled.\n      If this doesn't work for you on macOS, you can run this from a VM (preferably matching the server ISA).\n- If everything goes well, the server will reboot. Shortly after that you should be able to ssh into the server and get root access\n    - The server will also have a new SSH key, so you'll have to clear old records from `~/.ssh/known_hosts`\n\n### Adding basic configuration\n\n**Make sure you've removed the server's previous key from `~/.ssh/known_hosts` if you've connected to the server before!**\n\nFollowing successful installation, run `(cd postinstall \u0026\u0026 ./auto.sh \u003cserver_ip\u003e \u003cpath_to_your_ssh_key\u003e)` (once the server has rebooted). Or if you want to\ndo this manually:\n- ssh into the server and run `nixos-generate-config`\n- replace `/etc/nixos/configuration.nix` with `postinstall/configuration.nix` from this repo\n- copy `postinstall/flake.nix` to `/etc/nixos/flake.nix`\n- `nixos-rebuild switch`\n\n### Next steps\n\nConfigure your NixOS server as you want. The only things to keep in mind are:\n- there are no channels configured\n- it's using a flake for the system config and setting the nix path in `/etc/nixos/flake.nix`\n- the server's hostname is nixos\n\nYou may want to change the hostname, pull in some flake with system config for that particular hostname, or you\nmay want to just import some modules into your config.\n\n## Setting up a Laravel app\n\nAfter you have a NixOS server set up, you can use our `laravel.nix` module to start configuring Laravel sites.\n\nThe module is fairly generic so it should work for most sites. It's written in a simple way, to be as easy to\ncustomize as possible if needed, while offering enough customization for most applications.\n\nImport the module in your system flake and invoke it with these parameters:\n```nix\n(laravelSite {\n  name = \"mysite\";\n  domains = [ \"mysite.com\" ];\n  phpPackage = pkgs.php84;\n\n  ssl = true; # optional, defaults to false, affects *ALL* domains\n  extraNginxConfig = \"nginx configuration string\"; # optional\n  sshKeys = [ \"array\" \"of\" \"public\" \"ssh\" \"keys\" ]; # optional\n  extraPackages = [ pkgs.nodejs_24 ]; # optional\n  queue = true; # start a queue worker - defaults to false, optional\n  queueArgs = \"--tries=3\"; # optional, default empty\n  generateSshKey = false; # optional, defaults to true\n  poolSettings = { # optional - overrides all of our defaults\n    \"pm.max_children\" = 12;\n    \"php_admin_value[opcache_memory_consumption]\" = \"512\";\n    \"php_admin_flag[opcache.validate_timestamps]\" = true;\n  };\n  # alternatively:\n  extraPoolSettings = { # merged with poolSettings, doesn't override our defaults\n    \"pm.max_children\" = 12;\n  }\n})\n```\n\nThe module creates a new user (`laravel-${name}`), a `/srv/${name}` directory, configures\ncron to run every minute optionally starts a queue worker and configures php-fpm with\ngood defaults (see below). The user has a home directory in `/home/laravel-${name}`\n(used mainly for `./cache` used by composer and npm) and the site is served from the srv\ndirectory.\n\nThe default php-fpm opcache configuration is to cache everything *forever* without any\nrevalidation. Therefore, make sure to include `sudo systemctl reload phpfpm-${name}` in\nyour deployment script.\n\nTo deploy your app, you can use\n[ssh deployments](https://stancl.substack.com/i/170830424/setting-up-deployments),\nrather than webhooks triggering pull hooks or other techniques. Since this module\ncreates a new user for each site, this deployment technique becomes non-problematic\nand it's one of the simplest things you can do. Just ssh-keygen a private key, make a\nGitHub Actions job use that on push, and include the public key in the site's `sshKeys` array.\nThen, to be able to `git pull` the site on the server, add the user's `~/.ssh/id_ed25519.pub`\nto the repository's deployment keys. The ssh key for the user is generated automatically\n(can be disabled by setting `generateSshKey` to false).\n\nAlso, if you're using `ssl` you should put this line into your system config:\n```nix\nsecurity.acme.defaults.email = \"your@email.com\";\n```\n\nA full system config can look something like this (excluding any additional configuration\nyou may want to make):\n```nix\n{\n  description = \"System flake\";\n\n  inputs = {\n    nixpkgs.url = \"github:nixos/nixpkgs?ref=nixos-unstable\";\n  };\n\n  outputs = { self, nixpkgs, ... }@inputs: {\n    nixosConfigurations = let\n      system = \"aarch64-linux\";\n      pkgs = nixpkgs.legacyPackages.${system};\n      laravelSite = import ./laravel.nix;\n    in {\n      nixos = nixpkgs.lib.nixosSystem {\n        inherit system;\n\n        modules = [\n          {\n            nix.nixPath = [ \"nixpkgs=${inputs.nixpkgs}\" ];\n            security.acme.defaults.email = \"your@email.com\";\n          }\n          ./configuration.nix\n\n          # your (laravelSite { ... }) calls here\n        ];\n      };\n    };\n  };\n}\n```\n\nThere's a million different ways to structure your system flake, so you may prefer to use\nsomething different. Note that `laravel.nix` is explicitly not a flake and not a top-level\n\"input\" - the goal is to just invoke it each time *to change system configuration*. We don't\nwant an additional lockfile for the laravel module and we don't want to update the system\nlockfile whenever we make changes to the laravel module. With the most basic configuration,\nyou should only have `nixpkgs` in your lockfile.\n\nThere also isn't any special shell since Laravel is entirely handled by system daemons like\nnginx, php-fpm, cron, and optionally a queue worker systemd service. We do include a .bashrc\nwith some echos to quickly remind you of the filesystem structure and available commands.\n\nSimply `scp laravel.nix root@\u003cyour server ip\u003e:/etc/nixos/` and start writing config as above.\n\n### www redirects\n\nTo redirect `www.acme.com` to `acme.com`, you can use the `wwwRedirect` attribute. It should be\nnull for no redirect, or an integer status code for an enabled redirect.\n\n```nix\n(laravelSite {\n  name = \"foo\";\n  domains = [ \"foo.com\" ];\n  wwwRedirect = 301; # permanent redirect\n  # ...\n})\n```\n\nWith the config above, `www.foo.com/bar` will return a redirect to `foo.com/bar`, with the schema\nmatching the site's `ssl` config.\n\n### Default nginx server\n\nOut of the box, if nginx cannot match an incoming request's host to a specific virtual host it will\njust use _some_ vhost. You can prevent behavior that by adding a module like this:\n\n\u003e [!NOTE]\n\u003e You can also use the `catchall.nix` module here (which includes the code below):\n\u003e\n\u003e `scp catchall.nix root@\u003cserver ip\u003e:/etc/nixos/`\n\u003e\n\u003e Then just add `./catchall.nix` to your modules array.\n\n```nix\n{\n  services.nginx.virtualHosts.\"catchall\" = {\n    default = true;\n    locations.\"/\".return = \"444\";\n    rejectSSL = true;\n  };\n}\n```\n\nThis creates a `default_server` vhost that returns an empty response to any request. The name of the\nvhost is irrelevant.\n\n### Authenticated Origin Pulls (AOP)\n\nTo make your sites reachable ONLY using Cloudflare, you can use [authenticated origin\npulls](https://developers.cloudflare.com/ssl/origin-configuration/authenticated-origin-pull/).\n\nAOP basically ensures that any SSL traffic is using Cloudflare's \"client certificate\". With\nall non-HTTPS traffic being presumably force-redirected to HTTPS (`ssl = true;`).\n\nThis means that if someone discovers your server's IP, they can send requests bypassing\nCloudflare (and all the settings you may have there), but they will go nowhere. Nginx will\nsee they don't have the client certificate and simply return a 400 error (\"No required SSL\ncertificate was sent\"). The requests will never reach your Laravel application.\n\nThere are many ways this can be configured. Some people prefer using their own client certificates\nbut Cloudflare lets you use a default global one. That means less config and unless you have some\nvery special needs, it will work perfectly fine for this purpose.\n\nTo enable AOP on the server, simply set:\n```nix\ncloudflareOnly = true;\n```\n\nin the site config. This will automatically add:\n```nginx\nssl_verify_client on;\nssl_client_certificate \"path to Cloudflare's default cert\";\n```\n\nThen just enable AOP in the `SSL/TLS -\u003e Origin Server` setting of your CF zone.\n\n\u003e The only caveat with using AOP is that you will not be able to access your app directly\n\u003e *even from the same server* -- HTTP requests will be redirected to HTTPS and HTTPS will\n\u003e fail due to a missing certificate. **But this is generally not an issue in practice** since\n\u003e the server config we use doesn't use any special hosts records that'd try to bypass CF.\n\u003e So running `curl https://your-app.com` on the server will work without issues. The only\n\u003e thing that will NOT work is:\n\u003e ```sh\n\u003e curl --resolve your-app.com:443:127.0.0.1 https://your-app.com/\n\u003e curl --connect-to your-app.com:443:127.0.0.1:443 https://your-app.com/\n\u003e ```\n\u003e And any equivalents.\n\n### Using real_ip with Cloudflare\n\nIf you use Cloudflare, your access log (`/var/log/nginx/access.log`) will show Cloudflare IPs\ninstead of the actual remote IPs. This also affects what IPs are passed to php-fpm and therefore\nLaravel. If you don't care about the access log, you can just make a simple helper like this in\nPHP:\n\n```php\n\u003c?php\n\nfunction client_ip(): string\n{\n    if ($ipv6 = request()-\u003eheader('CF-Connecting-IPv6')) {\n        return $ipv6;\n    }\n\n    return request()-\u003eheader('CF-Connecting-IP') ?: request()-\u003eip();\n}\n```\n\nHowever a more proper solution is to use the `real_ip` module in common nginx config. To do that,\nwe can follow the [guide from the NixOS\nwiki](https://nixos.wiki/wiki/Nginx#Using_realIP_when_behind_CloudFlare_or_other_CDN).\n\n\u003e [!NOTE]\n\u003e You can also use the `realip.nix` module here (which wraps the code below):\n\u003e\n\u003e `scp realip.nix root@\u003cserver ip\u003e:/etc/nixos/`\n\u003e\n\u003e Then just add `./realip.nix` to your modules array.\n\n```nix\n# New module in your modules array\n{\n  services.nginx.commonHttpConfig =\n    let\n      realIpsFromList = lib.strings.concatMapStringsSep \"\\n\" (x: \"set_real_ip_from  ${x};\");\n      fileToList = x: lib.strings.splitString \"\\n\" (builtins.readFile x);\n      cfipv4 = fileToList (pkgs.fetchurl {\n        url = \"https://www.cloudflare.com/ips-v4\";\n        sha256 = \"0ywy9sg7spafi3gm9q5wb59lbiq0swvf0q3iazl0maq1pj1nsb7h\";\n      });\n      cfipv6 = fileToList (pkgs.fetchurl {\n        url = \"https://www.cloudflare.com/ips-v6\";\n        sha256 = \"1ad09hijignj6zlqvdjxv7rjj8567z357zfavv201b9vx3ikk7cy\";\n      });\n    in\n    ''\n      ${realIpsFromList cfipv4}\n      ${realIpsFromList cfipv6}\n      real_ip_header CF-Connecting-IP;\n    '';\n}\n```\n\nTo make `lib` accessible, also update:\n```diff\n nixosConfigurations = let\n   system = \"aarch64-linux\";\n   pkgs = nixpkgs.legacyPackages.${system};\n+  lib = pkgs.lib;\n   laravelSite = import ./laravel.nix;\n in {\n```\n\nTo check the up-to-date hashes, you can use:\n\n```sh\ncurl -s https://www.cloudflare.com/ips-v4 | sha256 | xargs nix hash convert --hash-algo sha256 --to nix32\ncurl -s https://www.cloudflare.com/ips-v6 | sha256 | xargs nix hash convert --hash-algo sha256 --to nix32\n```\n\n## Static sites\n\nFor hosting static sites, you can use `static.nix` very similarly to `laravel.nix`. Notable differences:\n1. `root` is required, e.g. `name=\"foo\"; root=\"build\";` means `/srv/foo/build` will be served. In other\n   words, even though this is for static sites, we do not serve the entire `/srv/{name}` dir to allow\n   for version control and build steps.\n2. By default, the `static-generic` user is used. Static sites do not always need strict user separation\n   since there's no request runtime. That said, the user is *very* limited and only has `pkgs.git` and\n   `pkgs.unzip`. Therefore it's only suited for static sites that are at most pulled from somewhere,\n   rather than built using Node.js. Also note that GitHub generally doesn't allow using a single SSH key\n   as the deploy key on multiple repos. For these reasons, it's still recommended to enable user creation\n   via `user = true;`.\n\nFull usage:\n```nix\n(staticSite {\n  name = \"foo\"; # name of the site\n  root = \"build\"; # directory within /srv/foo to be served by nginx\n\n  user = true; # if false, static-generic is used. Default: false\n  domains = [ \"foo.com\" \"bar.com\" ]; # domains to serve the site on\n  ssl = true; # enableACME + forceSSL. Default: false\n  # Status code for www-to-non-www redirects. No redirect if null. Applies to all sites\n  wwwRedirect = 301; # Default: null\n  cloudflareOnly = true; # use Authenticated Origin Pulls. See the dedicated section. Default: false\n  extraPackages = [ pkgs.nodejs_24 ]; # only applies if user=true\n  generateSshKey = true; # defaults to true, used even with user=false\n  sshKeys = [ \"array\" \"of\" \"public\" \"ssh\" \"keys\" ]; # optional\n  extraNginxConfig = \"nginx configuration string\"; # optional\n})\n```\n\n## Maintenance\n\nIt's a good idea to have `/etc/nixos` tracked in version control so you can easily revert the config\nincluding the lockfile, not just system state.\n\nThe only thing in your lockfile should be `nixpkgs` unless you add more inputs to your system config.\n\nAfter rebuilding the system several times, you will have some past generations and unused files in the Nix\nstore that can be cleaned up.\n\nList past generations with:\n```sh\nsudo nix-env --list-generations --profile /nix/var/nix/profiles/system\n```\n\nDelete old ones:\n```sh\nsudo nix-env --delete-generations old --profile /nix/var/nix/profiles/system\n```\n\nThen clean garbage:\n```sh\nsudo nix-collect-garbage -d\n```\n\n## Rebuilding\n\nFrom personal testing, running `nixos-rebuild switch` doesn't necessarily cause any downtime for users\nif your website is behind Cloudflare. NixOS first builds everything it needs and only then, usually pretty\nquickly, restarts (and adds, removes, etc) services as needed. This means your nginx **might** be down for\na very brief period, but if Cloudflare cannot connect to your server it will retry a couple of times. So at\nmost some requests will be very slightly delayed, but users should not see any errors on most rebuilds.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchtechx%2Fnix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farchtechx%2Fnix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchtechx%2Fnix/lists"}