# Apache httpd 2.4 configuration for cgit.
#
# This is a complete httpd.conf rather than a vhost snippet, so nothing here
# is included from elsewhere and no distro base config is assumed. Check it
# and run it with
# httpd -t -f /path/to/apache.conf # check the syntax
# httpd -f /path/to/apache.conf # run it
# To use it as an ordinary vhost file instead, drop everything above the
# virtual hosts and let your distro's httpd.conf supply it.
#
# Apache runs the cgit.cgi binary directly through mod_cgid, so no FastCGI
# bridge is needed.
#
# Paths assumed below, edit them to match your install.
# cgit CGI binary /usr/lib/cgit/cgit.cgi
# static assets /usr/share/cgit (cgit.css cgit.js cgit.png favicon.ico robots.txt)
# cgit config /etc/cgitrc
# public URL https://git.example.org/ (cgit at the domain root)
#
# cgit is one CGI executable. It learns the repository and the page from
# PATH_INFO and reads page options such as h= and id= from QUERY_STRING.
# ScriptAlias runs the binary and forwards the trailing path as PATH_INFO, so
# no extra path tuning is needed. cgit builds its own link base from
# SCRIPT_NAME. The five static assets are served straight off disk. mod_alias
# resolves Alias and ScriptAlias in order and the first match wins, so the
# static Alias lines come before the catch-all ScriptAlias to stop the two
# routes from shadowing each other.
# ServerRoot is what every relative path below resolves against, including the
# module paths. It is /etc/httpd on RHEL and Fedora and /etc/apache2 on Debian
# and Ubuntu, where the modules live in /usr/lib/apache2/modules and the
# LoadModule lines need that absolute path instead of the relative one.
ServerRoot /etc/httpd
PidFile /var/run/httpd.pid
# Where Apache puts its runtime scratch, the mutexes and the SSL session
# cache. It is /var/run/httpd on RHEL and Fedora and /var/run/apache2 on
# Debian and Ubuntu. The directory has to exist and be writable before Apache
# starts, which is normally the packaging's job.
DefaultRuntimeDir /var/run/httpd
Listen 80
Listen 443
# Set globally so Apache does not have to guess a name at startup, which it
# warns about. Each vhost overrides it with its own.
ServerName git.example.org
# Drop the version number from the Server header and from error pages.
ServerTokens Prod
ServerSignature Off
# mod_cgid suits the threaded MPMs that ship by default. Use mod_cgi instead
# only on the old prefork MPM. mod_alias provides Alias and ScriptAlias,
# mod_env provides SetEnv, and the rest are the core pieces a standalone
# config cannot do without. On Debian and Ubuntu run
# a2enmod cgid alias env headers expires ssl
# rather than editing these lines. The guards make double-loading harmless.
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule mime_module modules/mod_mime.so
LoadModule alias_module modules/mod_alias.so
LoadModule cgid_module modules/mod_cgid.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule expires_module modules/mod_expires.so
# TLS. Delete these two along with the HTTPS vhost to run plain HTTP only.
# mod_socache_shmcb backs the SSL session cache and mod_ssl expects it.
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so
# The user Apache drops to after binding the ports. It is apache on RHEL and
# Fedora, www-data on Debian and Ubuntu, and http on Arch.
User apache
Group apache
# The combined format comes from the distro config rather than from Apache
# itself, so a standalone config has to define it before any CustomLog uses it.
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
ErrorLog /var/log/apache2/error.log
LogLevel warn
# The usual "TypesConfig conf/mime.types" would pull in a second file. Exactly
# five static files are served off disk, so their types are declared here
# instead. Everything else Apache returns comes from cgit, which sets its own
# Content-Type.
AddType text/css .css
AddType text/javascript .js
AddType image/png .png
AddType image/vnd.microsoft.icon .ico
AddType text/plain .txt
# Deny the whole filesystem, then open only the two directories cgit needs.
# Without this a misplaced Alias could expose anything readable on the host.
AllowOverride None
Require all denied
# The static asset directory, read only.
Options None
AllowOverride None
Require all granted
# These assets rarely change, so let browsers cache them. Keep the
# caching scoped to this directory. cgit sends no caching headers of its
# own, so a server-wide ExpiresDefault would stamp freshness onto its
# pages, fight the no-store cgit puts on the login page, and could let
# one visitor's page be served to another from a shared cache.
ExpiresActive On
ExpiresDefault "access plus 30 days"
# The cgit binary.
# Allow CGI execution here. ScriptAlias implies it, stating it makes the
# intent clear.
Options +ExecCGI
# Run cgit.cgi as a CGI even if it is ever reached through a plain Alias
# rather than ScriptAlias.
SetHandler cgi-script
AllowOverride None
Require all granted
# This vhost only bounces plain HTTP up to HTTPS. It serves no cgit itself,
# every cgit directive lives in the HTTPS vhost below. To run without TLS for
# now, convert the HTTPS vhost to port 80 and delete this whole block rather
# than editing it, since deleting only the Redirect line would leave a vhost
# that serves nothing.
ServerName git.example.org
ErrorLog /var/log/apache2/cgit_error.log
CustomLog /var/log/apache2/cgit_access.log combined
Redirect permanent / https://git.example.org/
# The vhost that serves cgit. To run without TLS for now, change this opening
# line to port 80, delete the SSL lines, delete the Strict-Transport-Security
# line, and delete the vhost above so there is only one. Everything else stays
# as it is.
ServerName git.example.org
ErrorLog /var/log/apache2/cgit_ssl_error.log
CustomLog /var/log/apache2/cgit_ssl_access.log combined
# Point these at your certificate.
SSLEngine on
SSLCertificateFile /etc/ssl/certs/git.example.org.crt
SSLCertificateKeyFile /etc/ssl/private/git.example.org.key
# Subtractive rather than naming the versions to keep, since a mod_ssl
# built before TLS 1.3 rejects the +TLSv1.3 token outright and refuses to
# start. This form enables 1.3 wherever it exists.
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
# Which config cgit reads. It falls back to the compiled-in /etc/cgitrc,
# the same path used here, but setting it makes the location explicit and
# lets you point at a per-vhost file later.
SetEnv CGIT_CONFIG /etc/cgitrc
# The only request body cgit ever reads is the auth-filter login form, and
# it stops after 4096 bytes. Nothing else here accepts an upload.
LimitRequestBody 65536
# Site-wide security headers are set here so they also cover the static
# assets Apache serves. cgit itself sends only the headers the proxy
# cannot supply. Those are Status, Content-Type, Content-Length and
# Content-Disposition on downloads, a no-store Cache-Control on
# unauthenticated responses, the auth filter's Set-Cookie, and on raw
# repository bytes a nosniff of its own next to the stricter policy
# "default-src 'none'". Everything else, this policy included, is the
# proxy's job.
#
# The word setifempty is load bearing on the two headers cgit can also
# emit. "Header always set" replaces a same-named header even when the
# CGI sent it, which was verified against Apache 2.4.67 and would swap
# the strict policy on raw repository content for this looser site one.
# setifempty yields to whatever cgit sent and still covers every response
# without one, the HTML pages, the static assets, and with always also
# Apache's own error pages. Referrer-Policy stays a plain set because
# cgit never emits it, so there is nothing to overwrite.
#
# script-src stays self because cgit loads only its own cgit.js, and
# style-src allows inline for the diffstat bars. form-action self covers
# the login form, the only form cgit renders. If you enable the gravatar
# or libravatar avatar filter, add its host to img-src, for example
# https://www.gravatar.com.
Header always setifempty Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'"
Header always setifempty X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "no-referrer"
# The browser features a git viewer never asks for, camera and location
# among them, are refused outright for everything served here, repository
# files included.
Header always set Permissions-Policy "accelerometer=(), autoplay=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), usb=()"
# Cross-origin isolation. The opener policy cuts any window.opener link
# between cgit and pages that open it, and the resource policy stops
# other origins from embedding what cgit serves, a hotlinked raw file for
# example. git clients are not browsers and ignore both, so clone and
# snapshot downloads keep working. The stricter
# Cross-Origin-Embedder-Policy is deliberately absent because it would
# break the avatar filters mentioned above.
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Resource-Policy "same-origin"
# Two years of forced HTTPS. This config already redirects every plain
# request to TLS, so browsers may as well stop asking. Delete this line
# if you convert the vhost to plain HTTP, and know it is hard to undo
# once browsers have seen it.
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
# These five files are the only things served off disk. Each Alias maps
# one URL to one file. Because they come before the ScriptAlias below, a
# request for /cgit.css is answered from disk and never reaches cgit.
# cgit.css and cgit.js are the paths cgit's HTML points at by default, so
# if you relocate the assets update both these Alias targets and the css,
# js, logo and favicon settings in cgitrc to agree.
Alias /cgit.css /usr/share/cgit/cgit.css
Alias /cgit.js /usr/share/cgit/cgit.js
Alias /cgit.png /usr/share/cgit/cgit.png
Alias /favicon.ico /usr/share/cgit/favicon.ico
Alias /robots.txt /usr/share/cgit/robots.txt
# ScriptAlias maps a URL prefix to a path, marks it executable, and
# forwards the rest of the URL as PATH_INFO. Mapping / makes cgit the
# handler for every URL the static Aliases above did not already claim.
#
# The trailing slash on cgit.cgi/ is load bearing. It tells Apache that
# cgit.cgi is the program and the rest of the URL is PATH_INFO. So a
# request for /torvalds/linux/tree/kernel?h=next runs the binary with
# PATH_INFO set to /torvalds/linux/tree/kernel and QUERY_STRING set to
# h=next. cgit derives its link base from SCRIPT_NAME, which at the domain
# root is / and needs no tuning. For a sub-path install see the note below.
ScriptAlias / /usr/lib/cgit/cgit.cgi/
# The SSL session cache is a global mod_ssl setting, so it sits outside the
# vhosts. Resumption keeps a browser paging through a repository from redoing
# a full handshake on every connection. Delete along with the HTTPS vhost if
# you serve plain HTTP.
# Relative, so it lands in DefaultRuntimeDir and follows it across distros.
SSLSessionCache "shmcb:ssl_scache(512000)"
SSLSessionCacheTimeout 300
# To serve cgit at https://git.example.org/cgit/ instead of the root, change
# the ScriptAlias to
# ScriptAlias /cgit/ /usr/lib/cgit/cgit.cgi/
# and move the static assets under the same prefix, for example
# Alias /cgit/cgit.css /usr/share/cgit/cgit.css
# SCRIPT_NAME then becomes /cgit and cgit auto-detects it. If links come out
# wrong, pin the base in cgitrc with virtual-root=/cgit.