howtos

Setup git-shell user

We want to create a Linux user with git repositories in his home directories. External systems should be able to ssh+git to this user’s repos, but not be able to do general SSH login with this user.

create a new user

First, create a new user vrs, specify a home directory, and set the default shell to git-shell:

sudo useradd --create-home --home-dir /home/gitreps --shell /usr/bin/git-shell vrs

NOTE: to delete the new user, use sudo deluser --remove-home --remove-all-files vrs && sudo delgroup vrs.

You can see the list of available shells with the command:

cat /etc/shells

Even though /etc/shells might not list the shell /usr/bin/git-shell, the shell does exists (if you have installed git on the system).

Later on, if you need to login with the user for some reason, you can do so by assigning the user the /bin/bash shell:

sudo usermod --shell /bin/bash vrs

You can check the current user’s shell with the command:

echo $SHELL

Now, let’s setup the password for vrs:

sudo passwd vrs

Let’s add our main user to the new user’s group vrs:

sudo usermod --append --groups vrs valera

To make this change take effect right away (without logging out and back in), run the command sudo su - $USER.

setup new user’s home directory

We will clean up the vrs home directory, and set proper permissions:

sudo chmod 0775 /home/gitreps
sudo rm -rf /home/gitreps/.[!.]* /home/gitreps/.??*
ls -ahl /home/gitreps

NOTE 1: you should see an empty directory /home/gitreps at this point.

NOTE 2: permission 0775 translates to rwxrwxr-x.

From this point on, our regular user can create directories, and write files to the home directory of vrs.

Let’s setup the special directory git-shell-commands, and copy some boilerplate files from https://github.com/git/git/tree/master/contrib/git-shell-commands:

mkdir /home/gitreps/git-shell-commands

cat > /home/gitreps/git-shell-commands/no-interactive-login << \EOF
#!/bin/sh
printf '%s\n' "Hi $USER! You've successfully authenticated, but I do not"
printf '%s\n' "provide interactive shell access."
exit 128
EOF



cat > /home/gitreps/git-shell-commands/help << \EOF
#!/bin/sh

if tty -s
then
    echo "Run 'help' for help, or 'exit' to leave.  Available commands:"
else
    echo "Run 'help' for help.  Available commands:"
fi

cd "$(dirname "$0")"

for cmd in *
do
    case "$cmd" in
    help) ;;
    *) [ -f "$cmd" ] && [ -x "$cmd" ] && echo "$cmd" ;;
    esac
done
EOF



cat > /home/gitreps/git-shell-commands/list << \EOF
#!/bin/sh

print_if_bare_repo='
    if "$(git --git-dir="$1" rev-parse --is-bare-repository)" = true
    then
        printf "%s\n" "${1#./}"
    fi
'

find -type d -name "*.git" -exec sh -c "$print_if_bare_repo" -- \{} \; -prune 2>/dev/null
EOF



chmod u+x,g+x /home/gitreps/git-shell-commands/no-interactive-login
chmod u+x,g+x /home/gitreps/git-shell-commands/help
chmod u+x,g+x /home/gitreps/git-shell-commands/list



sudo chown --recursive vrs:vrs /home/gitreps/
sudo chgrp --recursive vrs /home/gitreps/

Now the setup of the home directory is done!

setup of ssh

Install the ssh server:

sudo aptitude install -y openssh-server

Proper configs for SSH server:

sudo rm -rf /etc/ssh_banner
sudo rm -rf /etc/ssh/sshd_config
sudo rm -rf /etc/ssh/ssh_config



sudo tee /etc/ssh/ssh_config << \EOF
Host *
    SendEnv LANG LC_*
    HashKnownHosts yes
    GSSAPIAuthentication yes
EOF



sudo tee /etc/ssh/sshd_config << \EOF
Port 7001
PermitRootLogin no
StrictModes yes
MaxAuthTries 6
MaxSessions 10
ClientAliveInterval 120

PubkeyAuthentication yes

PasswordAuthentication yes
# PasswordAuthentication no
PermitEmptyPasswords no

ChallengeResponseAuthentication no

UsePAM yes

X11Forwarding no
PrintMotd no

AcceptEnv LANG LC_*
Banner /etc/ssh_banner

AllowUsers vrs
EOF



sudo tee /etc/ssh_banner << \EOF
█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
█░░╦─╦╔╗╦─╔╗╔╗╔╦╗╔╗░░█
█░░║║║╠─║─║─║║║║║╠─░░█
█░░╚╩╝╚╝╚╝╚╝╚╝╩─╩╚╝░░█
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
EOF

Now, for the configs to take effect, depending on what your Linux distro is, you will either run (for Debian based distros):

sudo service ssh restart

or on Ubuntu 22.10 and later (and derivative distros):

sudo systemctl daemon-reload
sudo systemctl restart ssh.socket

or, a combination of both variants. It depends on subtle implementation differences in various distros. See discussion at SSHd now uses socket-based activation (Ubuntu 22.10 and later).

Make sure that there is no error from the ssh service:

sudo service ssh status

You should see that the ssh service is in active (running) state.

adding git repositories

The simplest way to create new git repositories, is to use our system user to issue git commands, and then sudo to modify the permissions of newly created git repo:

git init --bare /home/gitreps/new-git-repo.git
sudo chown --recursive vrs:vrs /home/gitreps/
sudo chgrp --recursive vrs /home/gitreps/

NOTE: The git command is being run with our standard system user. We are issuing the commands chown and chgrp for the new git repository to be fully owned by the user vrs. Going forward, we will be accessing it using SSH with user vrs.

working with git repos over SSH

Let’s test this setup locally. For ease of use, add an entry to your ~/.ssh/config:

Host laptop
  Hostname localhost
  Port 7001
  User vrs
  IdentitiesOnly no

Then you can clone the git repository:

mkdir -p ~/dev && cd ~/dev
git clone laptop:new-git-repo.git

You will see the SSH banner, and be prompted for a password (the one you set for the user vrs). If all was done correctly, you should be able to clone the repo to your local user directory.

If you have a remote machine which can access your system via the network, you can also test the ssh+git workflow from it. Just use the correct IP address of your system.

checking that regular SSH with git user is not possible

If you try to connect directly via SSH, you should see an error message:

$ ssh -p 7001 vrs@localhost
█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
█░░╦─╦╔╗╦─╔╗╔╗╔╦╗╔╗░░█
█░░║║║╠─║─║─║║║║║╠─░░█
█░░╚╩╝╚╝╚╝╚╝╚╝╩─╩╚╝░░█
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
vrs@localhost's password:

Last login: Fri Aug 15 05:00:54 2025 from 127.0.0.1
Hi vrs! You've successfully authenticated, but I do not
provide interactive shell access.
Connection to localhost closed.

You can however run the defined commands help, and list:

$ ssh -p 7001 vrs@localhost help
█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
█░░╦─╦╔╗╦─╔╗╔╗╔╦╗╔╗░░█
█░░║║║╠─║─║─║║║║║╠─░░█
█░░╚╩╝╚╝╚╝╚╝╚╝╩─╩╚╝░░█
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
vrs@localhost's password:
Run 'help' for help.  Available commands:
list
no-interactive-login


$ ssh -p 7001 vrs@localhost list
█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
█░░╦─╦╔╗╦─╔╗╔╗╔╦╗╔╗░░█
█░░║║║╠─║─║─║║║║║╠─░░█
█░░╚╩╝╚╝╚╝╚╝╚╝╩─╩╚╝░░█
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
vrs@localhost's password:
new-git-repo.git


$ ssh -p 7001 vrs@localhost no-interactive-login
█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
█░░╦─╦╔╗╦─╔╗╔╗╔╦╗╔╗░░█
█░░║║║╠─║─║─║║║║║╠─░░█
█░░╚╩╝╚╝╚╝╚╝╚╝╩─╩╚╝░░█
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
vrs@localhost's password:
Hi vrs! You've successfully authenticated, but I do not
provide interactive shell access.

And for any other commands, you will receive a generic unrecognized command error:

$ ssh -p 7001 vrs@localhost date
█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
█░░╦─╦╔╗╦─╔╗╔╗╔╦╗╔╗░░█
█░░║║║╠─║─║─║║║║║╠─░░█
█░░╚╩╝╚╝╚╝╚╝╚╝╩─╩╚╝░░█
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
vrs@localhost's password:
fatal: unrecognized command 'date'

references

Some technical info on git-shell:

My SSH setup docs:

Debian SSH docs:

Recent updates to SSH/SSHD on Ubuntu:

Passing ssh options to git:

Git safe.directory warnings:

about these howtos

This howto is part of a larger collection of howtos maintained by the author (mostly for his own reference). The source code for the current howto in plain Markdown is available on GitHub. If you have a GitHub account, you can jump straight in, and suggest edits or improvements via the link at the bottom of the page (Improve this page).

made with ❤ by Valera Rozuvan