With all of the various user and developer-hostile changes introduced to Twitter over the past year, the importance of a user-friendly alternative frontend for Twitter is greater than ever.

After using public instances of Nitter for a while, I wanted to try hosting my own instance. I thought it would be as simple as enabling a service in my NixOS configuration:

{
    services.nitter.enable = true;
}

Unfortunately, the only builds of Nitter that currently work are taken from a feature development branch which introduces changes both in build-time dependencies and runtime dependencies. You can read the full issue for the details (including how to generate guest accounts, which you’ll need later).

Here are my steps if you want to have a working instance of Nitter running on your NixOS server as of early November 2023:

{
  services.nitter = {
    enable = true;
    package = pkgs.nitter.overrideAttrs (old: {
      version = "guest_accounts";
      src = pkgs.fetchFromGitHub {
        owner = "zedeus";
        repo = "nitter";
        rev = "eaedd2aee7be6bc3dd2dceee09dc93052d0046f4";
        hash = "sha256-px0wyCYiI03DefIIF9+Xr95ChyASvg9N//cARFyRM5I=";
      };

      buildInputs =
        old.buildInputs
        ++ [
          (pkgs.nimPackages.buildNimPackage
            rec {
              pname = "oauth";
              version = "b8c163b0d9cfad6d29ce8c1fb394e5f47182ee1c";

              src = pkgs.fetchFromGitHub {
                owner = "CORDEA";
                repo = pname;
                rev = version;
                sha256 = "0k5slyzjngbdr6g0b0dykhqmaf8r8n2klbkg2gpid4ckm8hg62v5";
              };

              propagatedBuildInputs = [
                (pkgs.nimPackages.buildNimPackage
                  rec {
                    pname = "sha1";
                    version = "92ccc5800bb0ac4865b275a2ce3c1544e98b48bc";

                    src = pkgs.fetchFromGitHub {
                      owner = "onionhammer";
                      repo = pname;
                      rev = version;
                      sha256 = "sha256-tWHouIa6AFRmbvJaMsoWKNZX7bzqd3Je1kJ4rVHb+wM=";
                    };
                  })
              ];
            })
        ];
    });

    server.hostname = "subdomain.your.website";
    server.port = <CUSTOM_PORT>;
  };
}

In the snippet above we:

  • Override the package to point to the latest commit on the guest_accounts feature branch
  • Override the buildInputs list to include a new Nitter dependency, the oauth Nim package and its own dependency, the sha1 Nim package
  • Set our hostname and a custom port (the default port is likely to clash with other services we may have running)

Next, to make it available on that hostname, we can use Caddy (or Nginx, or whatever else you prefer):

{
  services.caddy = {
    enable = enableWebServer;
    virtualHosts = {
      "https://subdomain.your.website".extraConfig = ''
        reverse_proxy 127.0.0.1:<CUSTOM_PORT>
      '';
    };
  };
}
  • Make sure that you add a matching DNS record in your provider which points to the IP address of your server

Finally, we need to tell Nitter where to find the new runtime dependency, guest_accounts.json:

{
  systemd.services.nitter.serviceConfig.Environment = [
    "NITTER_CONF_FILE=/var/lib/private/nitter/nitter.conf"
    "NITTER_ACCOUNTS_FILE=/var/lib/private/nitter/guest_accounts.json"
  ];
}

We do this by overriding the Environment setting on the systemd service. The first entry for NITTER_CONF_FILE is taken from the source file of the service, and the second entry is added by us.

I was doing this in a bit of a rush so I didn’t automate the creation of the guest_accounts.json file in /var/lib/private/nitter. You can also do as I did and open a root shell with sudo su on the server, navigate to the directory and create the guest_accounts.json file filled with your guest credentials. (If you can suggest a clean way to provision files in /var/lib/private/* directories, please reach out!)

If you’re doing it like I did, the first time you apply the changes that enable the service, the nitter service will fail to start because the guest_accounts.json file doesn’t exist yet.

No problem. Hit sudo systemctl stop nitter, ensure that the file is in place, then sudo systemctl start nitter again when you’re ready.

If you have any questions you can reach out to me on Twitter and Mastodon.

If you’re interested in what I read to come up with solutions like this one, you can subscribe to my Software Development RSS feed.

If you’d like to watch me writing code while explaining what I’m doing, you can also subscribe to my YouTube channel.

Edit 11/11/2023: Some folks reached out with advice on how to automate the creation of the guest_accounts.json secret file; check out this post for more info!