Serving git's smart http protocol and a web interface with Apache
Posted on Tue 03 November 2015 in Git on the server
While git's original http protocol implementation was fairly dumb, the so-called smart http protocol is much better to use and rivals the ssh and git protocols in speed.
It can also be combined with a web-based repository viewer such as gitweb or cgit, allowing you to serve repositories and their web-based viewers from the same location. With bonus points for also supporting the dumb http protocol for really old git clients.
The git documentation has some example configurations, but not really one that ties all this together. The configuration below lets you create a git http server that lets users browse your repositories with cgit or gitweb, and clone using both dumb and smart http.
Download apache-http.conf
# Your domain name
Define SERVER_NAME git.example.com
# Path to your project root. Make sure it matches what's in cgitrc/gitweb.conf
Define GIT_PROJECT_ROOT /srv/gitroot
# Comment this out if you want to use the per-repository git-daemon-export-ok files
SetEnv GIT_HTTP_EXPORT_ALL
# Which features to use
Define USE_GITWEB
#Define USE_CGIT
Define USE_SMART_HTTP
Define USE_DUMB_HTTP
# Where do the applications live (these are Debian's defaults)
Define GIT_LIBDIR /usr/lib/git-core
Define GITWEB_SHAREDIR /usr/share/gitweb
Define CGIT_LIBDIR /usr/lib/cgit
Define CGIT_SHAREDIR /usr/share/cgit
# Below here nothing should need changing if you set the properties above correctly
<VirtualHost *:80>
ServerName ${SERVER_NAME}
ErrorLog ${APACHE_LOG_DIR}/${SERVER_NAME}/error.log
CustomLog ${APACHE_LOG_DIR}/${SERVER_NAME}/access.log combined
# Gitweb
<IfDefine USE_GITWEB>
<IfDefine USE_CGIT>
Error "Both gitweb and cgit are enabled. Please enable only one."
</IfDefine>
DocumentRoot ${GITWEB_SHAREDIR}
<Directory ${GITWEB_SHAREDIR}>
Options +FollowSymLinks +ExecCGI
AddHandler cgi-script .cgi
DirectoryIndex gitweb.cgi
Require all granted
</Directory>
RewriteEngine on
RewriteRule ^/(.*\.git(|(/(?!(HEAD|info|objects|refs|git-(upload|receive)-pack)).*)))$ \
/gitweb.cgi%{REQUEST_URI} [L,PT]
</IfDefine>
# CGit
<IfDefine USE_CGIT>
<Directory ${CGIT_LIBDIR}>
Options +FollowSymLinks +ExecCGI
AddHandler cgi-script .cgi
Require all granted
</Directory>
<Directory ${CGIT_SHAREDIR}>
Require all granted
</Directory>
RewriteEngine on
RewriteRule ^/(.*\.git(|(/(?!(HEAD|info|objects|refs|git-(upload|receive)-pack)).*)))?$ \
${CGIT_LIBDIR}/cgit.cgi/$1
Alias /cgit-css "${CGIT_SHAREDIR}"
</IfDefine>
# HTTP transports
<IfDefine USE_SMART_HTTP>
<Directory ${GIT_LIBDIR}>
Require all granted
</Directory>
SetEnv GIT_PROJECT_ROOT ${GIT_PROJECT_ROOT}
ScriptAliasMatch "^/(.*\.git/(HEAD|info/refs))$" \
${GIT_LIBDIR}/git-http-backend/$1
ScriptAliasMatch "^/(.*\.git/git-(upload|receive)-pack)$" \
${GIT_LIBDIR}/git-http-backend/$1
</IfDefine>
<IfDefine USE_DUMB_HTTP>
<Directory ${GIT_PROJECT_ROOT}>
Require all granted
</Directory>
<IfDefine !USE_SMART_HTTP>
AliasMatch "^/(.*\.git/(HEAD|info/refs))$" \
${GIT_PROJECT_ROOT}/$1
</IfDefine>
AliasMatch "^/(.*\.git/objects/(info/[^/]+|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx)))" \
${GIT_PROJECT_ROOT}/$1
</IfDefine>
</VirtualHost>
The following minimal configuration is needed for gitweb:
Download gitweb.conf
# path to git projects (<project>.git)
$projectroot = "/srv/gitroot";
# directory to use for temp files
$git_temp = "/tmp";
# git-diff-tree(1) options to use for generated patches
#@diff_opts = ("-M");
@diff_opts = ();
# Features
$feature{'pathinfo'}{'default'} = [1];
And for cgit, this is the absolute minimum
Download cgitrc
#
# cgit config
# see cgitrc(5) for details
css=/cgit-css/cgit.css
logo=/cgit-css/cgit.png
scan-path=/srv/gitroot
HTTPS and authentication
The configuration above is absolutely fine if you want world-readable read-only repositories and all your pushing is done over ssh. But if you want to accept pushes over https, you mush also enable authentication and SSL. The configuration below does this. It does not do anything with authorization though, it assumes that if you can authenticate, you can push. If you want more fine-grained authorization you can use per-repository update hooks to authorize your users.
As you'll see, the configuration is very similar to the http-only version. The differences are that all http traffic is redirected to https, ssl is enabled and secured using Mozilla's recommendations and that authentication is mandatory.
Download apache-https.conf
# Your domain name
Define SERVER_NAME git.example.com
Define SSL_CERT /etc/ssl/certs/${SERVER_NAME}.pem
Define SSL_KEY /etc/ssl/private/${SERVER_NAME}.key
# Path to your project root. Make sure it matches what's in cgitrc/gitweb.conf
Define GIT_PROJECT_ROOT /srv/gitroot
# Comment this out if you want to use the per-repository git-daemon-export-ok files
SetEnv GIT_HTTP_EXPORT_ALL
# Which features to use
Define USE_GITWEB
#Define USE_CGIT
Define USE_SMART_HTTP
Define USE_DUMB_HTTP
# Where do the applications live (these are Debian's defaults)
Define GIT_LIBDIR /usr/lib/git-core
Define GITWEB_SHAREDIR /usr/share/gitweb
Define CGIT_LIBDIR /usr/lib/cgit
Define CGIT_SHAREDIR /usr/share/cgit
# HTTP to HTTPS redirect
<VirtualHost *:80>
ServerName ${SERVER_NAME}
ErrorLog ${APACHE_LOG_DIR}/${SERVER_NAME}/error.log
CustomLog ${APACHE_LOG_DIR}/${SERVER_NAME}/access.log combined
RedirectMatch (.*) https://${SERVER_NAME}$1
</VirtualHost>
<VirtualHost *:443>
ServerName ${SERVER_NAME}
SSLEngine on
SSLCertificateFile ${SSL_CERT}
SSLCertificateKeyFile ${SSL_KEY}
SSLProtocol ALL -SSLv2 -SSLv3
SSLHonorCipherOrder on
SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
SSLCompression Off
ErrorLog ${APACHE_LOG_DIR}/${SERVER_NAME}/error.log
CustomLog ${APACHE_LOG_DIR}/${SERVER_NAME}/access.log combined
# Authentication
<Location />
AuthType Basic
AuthName "${SERVER_NAME} git access"
# You'll probably want to customize this, using ldap or pam authentication.
AuthBasicProvider file
AuthUserFile /etc/git-users
Require valid-user
</Location>
# Gitweb
<IfDefine USE_GITWEB>
<IfDefine USE_CGIT>
Error "Both gitweb and cgit are enabled. Please enable only one."
</IfDefine>
DocumentRoot ${GITWEB_SHAREDIR}
<Directory ${GITWEB_SHAREDIR}>
Options +FollowSymLinks +ExecCGI
AddHandler cgi-script .cgi
DirectoryIndex gitweb.cgi
Require all granted
</Directory>
RewriteEngine on
RewriteRule ^/(.*\.git(|(/(?!(HEAD|info|objects|refs|git-(upload|receive)-pack)).*)))$ \
/gitweb.cgi%{REQUEST_URI} [L,PT]
</IfDefine>
# CGit
<IfDefine USE_CGIT>
<Directory ${CGIT_LIBDIR}>
Options +FollowSymLinks +ExecCGI
AddHandler cgi-script .cgi
Require all granted
</Directory>
<Directory ${CGIT_SHAREDIR}>
Require all granted
</Directory>
RewriteEngine on
RewriteRule ^/(.*\.git(|(/(?!(HEAD|info|objects|refs|git-(upload|receive)-pack)).*)))?$ \
${CGIT_LIBDIR}/cgit.cgi/$1
Alias /cgit-css "${CGIT_SHAREDIR}"
</IfDefine>
# HTTP transports
<IfDefine USE_SMART_HTTP>
<Directory ${GIT_LIBDIR}>
Require all granted
</Directory>
SetEnv GIT_PROJECT_ROOT ${GIT_PROJECT_ROOT}
ScriptAliasMatch "^/(.*\.git/(HEAD|info/refs))$" \
${GIT_LIBDIR}/git-http-backend/$1
ScriptAliasMatch "^/(.*\.git/git-(upload|receive)-pack)$" \
${GIT_LIBDIR}/git-http-backend/$1
</IfDefine>
<IfDefine USE_DUMB_HTTP>
<Directory ${GIT_PROJECT_ROOT}>
Require all granted
</Directory>
<IfDefine !USE_SMART_HTTP>
AliasMatch "^/(.*\.git/(HEAD|info/refs))$" \
${GIT_PROJECT_ROOT}/$1
</IfDefine>
AliasMatch "^/(.*\.git/objects/(info/[^/]+|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx)))" \
${GIT_PROJECT_ROOT}/$1
</IfDefine>
</VirtualHost>