I host a private git server using Nginx. I'd like anyone to clone into my repos (without authorization) but require authorization if they try to push a commit.

My Nginx configuration is as follows:

server {
  listen 443 ssl;
  server_name git.example.com;
  ssl_certificate /fullchain.pem;
  ssl_certificate_key /privkey.pem;

  location ~ ^.*\.git/(HEAD|info/refs|objects/info/.*|git-(upload|recieve)-pack) {
    root /usr/share/nginx/git;

# --- incorrect solution ---

#    if ($1 = git-upload-pack) {
#        auth_basic "Restricted";
#        auth_basic_user_file /usr/share/nginx/htpasswd;
#    }
    client_max_body_size 0;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
    fastcgi_param GIT_HTTP_EXPORT_ALL "";
    fastcgi_param GIT_PROJECT_ROOT $realpath_root;
    fastcgi_param REMOTE_USER $remote_user;
    fastcgi_param PATH_INFO $uri;
    fastcgi_param unix:/var/fcgiwrap.socket;


From my understanding, a git push request sends a git-receive-pack to my server. My simple solution was to capture this suffix with $1 and use an if statement, but I found quickly that this is not the correct use for ifs (ifisevil).

Is there a more appropriate solution to what I'm trying to accomplish?


git could send different requests on push (e. g. /path/to/repo.git/path/in/repo/refs?service=git-upload-pack or similar), and you was trying to use equal comparision.

Try something like that:

  # static repo files for faster cloning over https:
  location ~ \.git/objects/(?:[0-9a-f]+/[0-9a-f]+|pack/pack-[0-9a-f]+\.(?:pack|idx))$ {
    root /home/git/repositories/;

  # requests that need to go to git-http-backend:
  location ~ \.git/(?:HEAD|info/refs|objects/info/|git-(?:upload|receive)-pack$) {
    root /home/git/repositories;
    # if upload:
    if ( $uri ~* "git-upload-pack$" ) {
       auth_basic ...

But also as working scenario you can use something like that:

    # authorize if user name is specified:
    if ( $remote_user != "" ) {
       auth_basic ...

or simply different URL (and location) for push that rewrite it after authorization. Then you can configure remote with different push-url (containing a user name or other location):

# with user:
git remote set-url --push origin https://user@git.domail.tld/repo/...
# with "rw" location (to force authorization):
git remote set-url --push origin https://git.domail.tld/rw/repo/...

By the way, directive "if" is not more evil as some top level regex location (especially if it is applied in certain location only and doesn't bother in http/server section directly).
But you can indeed do that all also without "if" (like in example below with named location), but it is not so simple to debug on some issues (if you're not familiar with nginx):

location ...condGit... {
  root ...;

  location ...condA... {
    ## authorize and supply request to @gitweb:
    auth_basic ...
    try_files "" @gitweb;

  location ...condB... {
    ## authorize and supply request to @gitweb:
    auth_basic ...
    try_files "" @gitweb;

  # send anything else to gitweb if it's not a real file (inside root of gitweb):
  try_files $uri @gitweb;

  location @gitweb {
    # ... single location of fcgi wrapper of git http backend ...
  • Thanks @sebres for your response. auth_basic is not allowed in an if context (doc). – pairwiseseq May 2 '20 at 18:42
  • you can use a named location with auth_basic inside and try_files to this location either in if block or in some location matching authorized requests (as in second example). – sebres May 4 '20 at 10:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.