# nginx configuration for cgit. # # nginx cannot run CGI programs itself, so a small bridge called fcgiwrap # runs the cgit.cgi binary and speaks FastCGI to nginx. Starting fcgiwrap is # covered in the notes at the end of this file. # # 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, so # nginx must pass PATH_INFO through to the binary. cgit builds its own link # base from SCRIPT_NAME. The five static assets are served straight off disk # and must never be routed through cgit. Passing PATH_INFO through is the # single most important part of the config below. # --- Optional HTTP to HTTPS redirect ---------------------------------------- # Delete this whole server block if you serve plain HTTP only. server { listen 80; listen [::]:80; server_name git.example.org; # ACME http-01 challenge files, if you use certbot in webroot mode. location ^~ /.well-known/acme-challenge/ { root /var/www/html; } # Everything else moves to HTTPS. location / { return 301 https://$host$request_uri; } } # --- Main site -------------------------------------------------------------- # Written for TLS on 443. For a quick plain-HTTP test, change the two listen # lines to port 80, delete the redirect block above, and delete the four ssl # lines below. Everything else stays the same. server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name git.example.org; ssl_certificate /etc/letsencrypt/live/git.example.org/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/git.example.org/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # --- Security headers --------------------------------------------------- # These sit here, not in cgit, because they must also cover the static # assets nginx serves directly. cgit loads only its own /cgit.js and uses # inline style on the diffstat bars, so script-src stays self while # style-src allows inline. always applies them to error responses too. If # you enable the gravatar or libravatar avatar filter, add its host to # img-src, for example https://www.gravatar.com or https://seccdn.libravatar.org. add_header 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'" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer" always; # Enable only once you serve HTTPS exclusively, since it is hard to undo. #add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; # The document root is the directory that holds the static assets. cgit # emits absolute links to /cgit.css and /cgit.png by default, so those # files must resolve at the root of the URL space. Pointing root at the # asset directory makes /cgit.css map to /usr/share/cgit/cgit.css. root /usr/share/cgit; # Upload cap for large form posts. Snapshots are generated rather than # uploaded, so this does not limit them. client_max_body_size 64m; access_log /var/log/nginx/cgit.access.log; error_log /var/log/nginx/cgit.error.log; # --- Static assets, served directly ------------------------------------- # Match the assets by their exact root-level names, never by bare # extension. cgit routes on PATH_INFO and a repository can hold files # ending in .css or .png, so /myrepo/tree/style.css and /myrepo/plain/ # logo.png are real cgit URLs. A broad extension match would capture # those, look for them on disk, and return 404 before cgit could render # them. Anchoring the regex at the start of the path matches /cgit.css but # not /myrepo/tree/cgit.css, so it can never shadow a repository file. An # nginx regex location is matched before the prefix location below, so # these assets win for their exact URLs and cgit wins for the rest. location ~ ^/(cgit\.css|cgit\.js|cgit\.png|favicon\.ico|robots\.txt)$ { expires 30d; access_log off; try_files $uri =404; } # --- cgit, the catch-all ------------------------------------------------ # Everything that is not a static asset above is a cgit URL, the repo # index, a repository, a page within a repository, a snapshot, a feed. location / { # nginx's standard FastCGI parameters, some of which are overridden # below. A later fastcgi_param wins, so include order does not matter. include fastcgi_params; # The program fcgiwrap runs. It must be the cgit binary itself, not # $document_root$fastcgi_script_name, which would try to run a repo # path and is the usual cause of a failed request. fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi; # cgit builds its link base, the virtual root, from SCRIPT_NAME. The # stock parameters set SCRIPT_NAME to the whole request path, which # would make cgit prepend that path to every link. Served at the # domain root the script has no prefix, so force SCRIPT_NAME empty and # cgit uses / as its base. A sub-path install sets it instead, see the # end of this file. fastcgi_param SCRIPT_NAME ""; # How cgit learns the repository and page. At the domain root the # whole request path is the PATH_INFO. fastcgi_param PATH_INFO $uri; # Page options such as h=branch, id=sha and the snapshot format. fastcgi_param QUERY_STRING $query_string; # Where the static assets live, kept consistent with root above. fastcgi_param DOCUMENT_ROOT $document_root; # The browser's Host header, so cgit builds clone URLs against the # name the visitor used rather than server_name. fastcgi_param HTTP_HOST $http_host; # Which config cgit reads. It checks CGIT_CONFIG and falls back to the # compiled-in /etc/cgitrc. Setting it makes the location explicit and # lets you move cgitrc without recompiling. fastcgi_param CGIT_CONFIG /etc/cgitrc; # The real scheme, so cgit builds correct https clone URLs. fastcgi_param HTTPS $https if_not_empty; # Hand off to the fcgiwrap socket. See the notes for how to create it. # A TCP fcgiwrap would use for example 127.0.0.1:9000 here. fastcgi_pass unix:/run/fcgiwrap.socket; # Large outputs such as snapshot tarballs and blame on big files can # take a while, so give cgit room and stream rather than buffer. fastcgi_read_timeout 300s; fastcgi_buffering off; } } # --- Notes, starting fcgiwrap ----------------------------------------------- # cgit is a CGI binary and fcgiwrap is the CGI to FastCGI bridge nginx talks # to. On Debian and Ubuntu the packaged systemd socket provides # /run/fcgiwrap.socket, so enabling it is enough. # apt install fcgiwrap # systemctl enable --now fcgiwrap.socket # The socket must be readable by nginx's user. The packaged unit runs fcgiwrap # as www-data, which nginx also uses on those systems. Without systemd you can # run # spawn-fcgi -s /run/fcgiwrap.socket -M 660 -- /usr/sbin/fcgiwrap # or run fcgiwrap over TCP and point fastcgi_pass at 127.0.0.1:9000. # --- Alternative, serving cgit under a sub-path ----------------------------- # To serve cgit at https://git.example.org/cgit/ instead of the root, split # the URL so SCRIPT_NAME is the prefix and PATH_INFO is the rest. # # location /cgit/ { # include fastcgi_params; # fastcgi_split_path_info ^(/cgit)(/.*)$; # fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi; # fastcgi_param SCRIPT_NAME $fastcgi_script_name; # fastcgi_param PATH_INFO $fastcgi_path_info; # fastcgi_param QUERY_STRING $query_string; # fastcgi_param HTTP_HOST $http_host; # fastcgi_param CGIT_CONFIG /etc/cgitrc; # fastcgi_param HTTPS $https if_not_empty; # fastcgi_pass unix:/run/fcgiwrap.socket; # } # # Serve the assets from the sub-path too, again anchored to the exact names. # # location ~ ^/cgit/(cgit\.css|cgit\.js|cgit\.png|favicon\.ico|robots\.txt)$ { # alias /usr/share/cgit/$1; # expires 30d; # access_log off; # } # # cgit's default css=/cgit.css and logo=/cgit.png point at the domain root, so # under a sub-path also set css=/cgit/cgit.css and logo=/cgit/cgit.png in # cgitrc. cgit derives the /cgit prefix from SCRIPT_NAME. If links come out # wrong, pin it in cgitrc with virtual-root=/cgit/.