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.
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
.
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!
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.
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
.
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.
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'
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:
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