# 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.