The docs for git-http-backend have instructions on how to set up the service in Apache, but they stop short of giving an example for Nginx setup. There are other bloggers who have posted bits and pieces of the installation instructions, but nothing that works end-to-end. The goal of this blog post is to share instructions on getting git-http-backend up and running from a clean slate. More specifically, this post covers:

  1. Installing the necessary software on CentOS
  2. Creating an authentication/authorization service for customized HTTP authentication
  3. Putting all the pieces together in your Nginx config

Step 1: Installing the Software

There are a number of tools we need to install:

  • git-http-backend, which is the CGI utility that performs actions on your Git repos.
  • fcgiwrap, which listens for FastCGI requests on a UNIX socket and passes them to git-http-backend
  • spawn-fcgi, which daemonizes fcgiwrap
  • Optional: openresty, which bundles nginx with the bindings required for customized user authentication

git-http-backend, spawn-fcgi, and openresty are all available in yum.

$ sudo yum install git spawn-fcgi openresty

Unfortunately, fcgiwrap is not currently available in yum, so we need to build it ourselves. Here is how I did it:

$ sudo yum install fcgi-devel
$ sudo yum groupinstall 'Development Tools'
$ sudo su root
# cd /root
# git clone git://github.com/gnosek/fcgiwrap.git
# cd fcgiwrap
# autoreconf -i
# ./configure
# make
# make install

This installs the binary into /usr/local/sbin/fcgiwrap.

If you haven't already done so, create a new unprivileged user git for handling requests to the git repositories. Also add nginx to the usergroup for git so that it can use the FCGI socket.

$ sudo useradd git
$ sudo usermod -a -G git nginx

We now need to configure spawn-fcgi. Edit the file /etc/sysconfig/spawn-fcgi and add the following content:

SOCKET=/var/run/fcgi-git.sock
OPTIONS="-u git -g git -s $SOCKET -S -M 0660 -P /var/run/fcgi-git.pid -- /usr/local/sbin/fcgiwrap -f"

Most of the options are standard. -u git -g git sets the FCGI user to git so that it can read and write your repositories. -s $SOCKET -S _m 0660 creates the socket with permissions 0660 so that nginx, which is in the git usergroup, can communicate on the socket. -P /var/run/fcgi-git.pid saves the FCGI process ID in the stated file. -- /usr/local/sbin/fcgiwrap runs fcgiwrap, and the -f tells fcgiwrap to print errors on stderr so that CGI errors show up in the NGINX error log file.

Now enable and start spawn-fcgi so that it runs now and at startup:

$ sudo systemctl enable spawn-fcgi
$ sudo systemctl start spawn-fcgi

If everything went correctly, you should have a new UNIX socket located at /var/run/fcgi-git.sock which is owned by user git and is ready to start accepting requests.

Step 2: Creating an Authentication Service

If you want custom authentication (for example, against a user database), and you want to have certain users able to access certain repositories, you need to set up a custom authentication service. You can do this using the auth_request module, which is available in OpenResty (see above) but not in the default Nginx distribution in yum. (Note: you can also do this using a custom Lua script, but that is harder to maintain because it requires building Nginx from source.)

In order to use the auth_request module, you need to create am HTTP server that responds with 400 status codes if a user is not authenticated and 200 status codes if the user is authenticated. Here is an example server in Node.JS (it depends on the npm package "basic-auth"):

#!/usr/bin/env node
"use strict";

const basicAuth = require("basic-auth");
const fs = require("fs");
const http = require("http");
const path = require("path");

// Listen on a UNIX Socket:
const SOCKET_PATH = "/var/run/nginx_socks/auth.sock";
try { fs.unlinkSync(SOCKET_PATH); } catch(err) {}
console.log("Listening on UNIX socket " + SOCKET_PATH);

const server = http.createServer((req, res) => {
    const originalUri = path.normalize(req.headers["x-original-uri"] || "");
    // The user is requesting access to originalUri.  Perform user authentication.
    // To display a login box on the client side, do this:
    res.writeHead(401, { "WWW-Authenticate": "Basic realm='"Git Repositories"'" });
    return res.end();
    // To access the username and password, do this:
    if (req.headers["authorization"]) {
        const creds = basicAuth.parse(req.headers["authorization"]);
        // username is in creds.name; password is in creds.pass
    }
    // To signal that the user is authorized to access the repository, do this:
    res.writeHead(204);
    return res.end();
});
server.listen(SOCKET_PATH);

Create the directory /var/run/nginx_socks with owner nginx so that Nginx can communicate with your authentication service:

$ sudo mkdir /var/run/nginx_socks
$ sudo chown nginx:nginx /var/run/nginx_socks

To daemonize the authentication server, create /usr/lib/systemd/system/git-auth.service with the following content (substitute the path to your app.js in the second line):

[Service]
ExecStart=/path/to/your/nodejs/app.js
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=git-auth
User=nginx
Group=nginx
Environment=NODE_ENV=production
Restart=always

[Install]
WantedBy=multi-user.target

Then run the following commands to enable the service.

$ sudo systemctl daemon-reload
$ sudo systemctl enable git-auth
$ sudo systemctl start git-auth

Putting it Together in Nginx Config

Here is an example Nginx configuration to listen on port 3000 with https. Note that I have a file /etc/nginx/default.d/ssl.conf with SSL configurations.

server {
    listen 3000 ssl;
    server_name _;
    include /etc/nginx/default.d/*;

    location / {
        auth_request      /auth;
        auth_request_set  $auth_status $upstream_status;

        # Git HTTP Backend
        client_max_body_size 0;
        fastcgi_param SCRIPT_FILENAME /usr/libexec/git-core/git-http-backend;
        fastcgi_param GIT_HTTP_EXPORT_ALL "";
        fastcgi_param REMOTE_USER $remote_user;
        fastcgi_param GIT_PROJECT_ROOT /home/git;
        fastcgi_param PATH_INFO $uri;
        fastcgi_pass unix:/var/run/fcgi-git.sock;
        include fastcgi_params;
    }

    location = /auth {
        internal;
        proxy_pass               http://unix:/var/run/nginx_socks/auth.sock:/;
        proxy_pass_request_body  off;
        proxy_set_header         Content-Length "";
        proxy_set_header         X-Original-URI $request_uri;
    }
}

Note how Nginx will send requests to /var/run/nginx_socks/auth.sock for authentication and then to /var/run/fcgi-git.sock for git manipulation.

Conclusion

There are a lot of moving pieces! Let me know in the comments if this post helped you and suggestions for improvements.