Setting up stand-alone Gerrit

28 August 2025
My first experience with continuous integration and the Git eco-system in general was Gerrit which I used professionally in the mid to late 2010s and since then have held positive memories of my experiences with it. Having been introduced to GitLab by my current company my feeling was of it being an inferior system to Gerrit so a few months ago decided to install Gerrit myself and give it a try to see how it matches up to my modern expectations. However one aspect of Gerrit that certainly is something of a pain is getting it up and running, not helped by the VPS that was being used to test it getting decomissioned. This is the write-up of figuring out how to get a stand-alone Gerrit installation working with Nginx and LLDAP.

Nginx

From the outset the web interfaces were to be proxied via Nginx so they could be secured using SSL and the server snippet below presents all the important details. The server had previously been setup using Let's Encrypt webroot validation for obtaining SSL certificates since these days traditional unsecured HTTP is now the rare exception. The trailing slash on the proxy_pass directives is important since LLDAP wants the matching /ldap/ URL prefix to be stripped whereas Gerrit's setup wants /gerrit/ retained.

server { listen 443 ssl; ssl_certificate /etc/live/example.org/fullchain.pem; ssl_certificate_key /etc/live/example.org/privkey.pem; set_real_ip_from 127.0.0.1; real_ip_header X-Forwarded-For; location / { root /var/www/html; index index.html; } location /ldap/ { proxy_pass http://127.0.0.1:17170/; } location /gerrit/ { proxy_pass http://127.0.0.1:8081; 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; } }

LLDAP

LDAP (Lightweight Directory Access Protocol) is in implementation not exactly lightwight but for something of this scale LLDAP is sufficently simplified that setting it up is not a project in itself. Documentation is rather container-centric but thankfully there are not many configuration parameters needed to get things going. It was built from source seperately with the lldap binary and the app resource directory manually copied into /opt/LLDAP. Below is a minimalist lldap_config.toml although some of the parameters specified are actually defaults.

verbose=false ldap_host = "127.0.0.1" ldap_port = 3890 http_host = "127.0.0.1" http_port = 17170 http_url = "https://example.org/ldap" assets_path = "/opt/LLDAP/app" jwt_secret = "hQ0_:nGN%Ie0=+D+]GBjO!!!)zuXQjW9" ldap_base_dn = "dc=example,dc=org" ldap_user_dn = "admin" ldap_user_email = "ldap@example.org" ldap_user_pass = "sekret!!" # force_ldap_user_pass_reset = false database_url = "sqlite:///opt/LLDAP/users.db?mode=rwc" key_file = "" key_seed = "RanD0m-STR!ng$" [smtp_options] enable_password_reset=false [ldaps_options] enabled=false

Unless force_ldap_user_pass_reset is set to true the ldap_user_* details are only used the first time LLDAP is started.

Putting LLDAP into background

LLDAP does not have any background mode so the following script can be used to daemonise it, with redirects is place so that nohup does not generate an output file. Probably not the best way of doing this but it is certainly the most expedient.

#!/bin/bash echo "Starting LLDAP.." nohup /opt/LLDAP/lldap run -c /opt/LLDAP/lldap_config.toml 2>&1 >/dev/null &

Setting up LDAP query user

My choice was to setup a dedicated LDAP user entry gerrit for use by Gerrit for doing authentication lookups, as this to me was preferable than using the admin account for the purpose. This can be done through the web interface at https://example.org/ldap/ and this LDAP user will need to be in the lldap_password_manager group.

Testing LDAP server lookups

To test that everything is working with LLDAP the following will use the credentials of the LDAP query user gerrit to search for user remy:

ldapsearch -x -D "uid=gerrit,ou=people,dc=example,dc=org" \ -H ldap://localhost:3890 -w "PassW0rd"\ -b "ou=people,dc=example,dc=org" -s sub 'uid=remy'

Coming up with this command-line was a case of what worked for me and the parameters are as follows:

-x
Don't use SASL authentication.
-D
Destinguish Name (i.e. LDAP user) to bind to. This is the identity used for logging into the LDAP server.
-H
LDAP server address.
-w
Password. There are alternative parameters that avoid putting the password on the command-line.
-b
Search base starting point
-s sub
Extend search to sub-trees (the default)
If the user was found there should be # numEntries: 1 on the last line of output and earlier lines give details of the user. On its own result: 0 Success only indicates the search was successfully performed and not whether the search found anything.

Gerrit Setup

With LLDAP operational the next stage is to setup Gerrit itself. Gerrit itself is written in Java so it is assumed that the latest version of OpenJDK 24 which at time of writing is 24.0.2 and that /opt/jdk-24.0.2 is where it has been installed. It will also assume a non-root user gerrit has been created.

Running Gerrit installer

This guide will use the gerrit-3.12.2.war distribution from the Gerrit releases page and it will be installed into /opt/Gerrit so the following commands will create it with the required ownerships and then run the installation as the non-root gerrit user. Running the installer as non-root avoids needing to fix up permissions later.

mkdir /opt/Gerrit chown gerrit:users /opt/Gerrit su gerrit /opt/jdk-24.0.2/bin/java -jar gerrit-3.12.2.war init -d /opt/Gerrit

The installation procedure will ask for several configuration options so the edited output below shows the values for the ones that require something other than the given default. For the two password prompts LDAP username found it easier to just press enter and then configure the password manually afterwards.

*** User Authentication Authentication method [openid/?]: ldap LDAP server [ldap://localhost]: ldap://localhost:3890 LDAP username : uid=gerrit,ou=people,dc=example,dc=org Account BaseDN : ou=people,dc=example,dc=org Group BaseDN [ou=people,dc=example,dc=org]: ou=groups,dc=example,dc=org *** HTTP Daemon Behind reverse proxy [y/N]? y Proxy uses SSL (https://) [y/N]? y Subdirectory on proxy server [/]: /gerrit Listen on address [*]: 127.0.0.1 Canonical URL [https://127.0.0.1/gerrit]: https://example.org/gerrit *** Plugins Install plugin delete-project version v3.12.2 [y/N]? y Install plugin download-commands version v3.12.2 [y/N]? y Install plugin hooks version v3.12.2 [y/N]? y Install plugin plugin-manager version v3.12.2 [y/N]? y Install plugin reviewnotes version v3.12.2 [y/N]? y

The choice of plugins is really up to personal taste butdownload-commands is particularly useful as it provides many different ways to fetch patch-sets and it is a bit of a pain to manually installer at a later time. Once done there wiill be the following message but before using this URI some tweaking is required.

Please open the following URL in the browser: https://example.org/gerrit/#/admin/projects/

Post-setup tweaking

The main configuration file /opt/Gerrit/etc/gerrit.config will need tweaking to the [ldap] section to add the parameters `supportAnonymous, password, accountPattern, and groupMemberPattern. After this it should look something like that below:

[gerrit] basePath = git canonicalWebUrl = https://example.org/gerrit serverId = 4cbb7e80-3520-48a3-aff8-4e434e9e5a28 [container] javaOptions = "-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance" javaOptions = "-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance" user = gerrit javaHome = /opt/jdk-24.0.1 [index] type = lucene [auth] type = LDAP gitBasicAuthPolicy = HTTP userNameCaseInsensitive = true [ldap] server = ldap://localhost:3890 accountBase = ou=people,dc=example,dc=org groupBase = ou=groups,dc=example,dc=org supportAnonymous = false username = "uid=gerrit,ou=people,dc=example,dc=org" password = "PassW0rd" accountPattern = (&(objectClass=person)(uid=${username})) groupMemberPattern = (&(objectClass=group)(member=${dn})) [receive] enableSignedPush = false [sendemail] smtpServer = localhost [sshd] listenAddress = *:29418 [httpd] listenUrl = proxy-https://127.0.0.1:8081/gerrit [cache] directory = cache

Once this tweaking is done Gerrit will need to be restarted:

/opt/Gerrit/bin/gerrit.sh restart

This command also takes other options such as start and stop to start and stop Gerrit respectively. The run command which runs Gerrit in the foreground is useful for trouble-shooting.

Using Gerrit

The first user to log into Gerrit is made an admin user and first port of call is the project setup page at https://example.org/gerrit/#/admin/projects/. There is a getting started walkthrough aimed at GitHub users so the Gerrit workflow is beyond the scope of this article.

Adding SSH/HTTP links to patchset download

The way I was taught to use Gerrit involves pulling down patches and changesets via SSH/HTTP which requires the download-commands plugin. To enable it add the following section to /opt/Gerrit/etc/gerrit.config and restart Gerrit.

[download] command = branch command = checkout command = cherry_pick command = pull command = format_patch command = reset scheme = ssh scheme = http scheme = depot_tools scheme = repo recurseSubmodules = true

Authentication headaches

By far the biggest headache getting things working was constantly getting Authentication unavailable at this time regardless of how much fiddling with settings is done, which via use of tcpdump and comparing packets with ldapsearch was tracked down to Gerrit using an old password. The culpret turned out to be /opt/Gerrit/etc/secure.config which seemed to be some sort of cache file and deleting that file sorted out the problem, although it is unclear what generated that file as I haven't seen it since.

The low-down

Gerrit limits itself to changesets whereas GitLab merge requests are branches, and whereas the latter brings flexibility it feels like it was designed to act as an all-inclusive web interface to Git rather than follow a thought-out workflow. Anything that requires a Git force-push just feels unholy to me and while there have been circumstances I have used multi-changeset merge requests, I think the flexibility comes with GitLab making it a lot easier to badly mess things up. With Gerrit I never had problems going back to a previous push but with GitLab looking back at development history is a lot harder, to the extent that I use multiple local repository clones and even sometimes generate patch files. I still think Gerrit is a better system for reviews but at times the limitation of single changeset commits is just too restrictive.