Setting Up a Self-Hosted Git Server on a Raspberry Pi with Apache, HTTPS, and Correct Permissions
Published May 12, 2026, 7:21 p.m. by james
A Raspberry Pi can be used as a small private Git server. The clean approach is to create a bare Git repository on the Raspberry Pi, store it in a shared location such as /var/www/git, and set permissions so Git can write to the repository when you push changes from another computer.
The most important part of this setup is ownership and permissions. A bare Git repository is not just a folder of source files. When a push happens, Git writes objects, refs, lock files, and pack files inside the repository. If the user handling the push does not have write permission, the push will fail.
1. Install Git and Apache
sudo apt update
sudo apt install git apache2
If the repository will be served through Apache over HTTP or HTTPS, enable the required Apache modules:
sudo a2enmod cgi alias env
sudo systemctl restart apache2
2. Create a Shared Git Group
Create a shared group for Git repository access:
sudo groupadd -f gitgroup
Add your normal Raspberry Pi user to the group:
sudo usermod -aG gitgroup "$USER"
If Apache needs to serve or receive Git data over HTTP or HTTPS, add Apache's user to the same group:
sudo usermod -aG gitgroup www-data
Log out and log back in so the new group membership becomes active. Then check your groups:
groups
id
id -nG
The repository should not normally be owned by www-data. A better setup is to make the repository owned by your normal server user and assign it to gitgroup. Apache can then access the repository through group permission instead of owning the whole repository.
3. Create the Main Git Directory
Create a directory to store all bare Git repositories:
sudo mkdir -p /var/www/git
Give ownership to your normal user and assign the directory to the shared Git group:
sudo chown -R "$USER":gitgroup /var/www/git
Set directory permissions:
sudo find /var/www/git -type d -exec chmod 2775 {} \;
Set file permissions:
sudo find /var/www/git -type f -exec chmod 664 {} \;
The 2 in 2775 enables the setgid bit. This makes newly created files and directories inherit the gitgroup group. That keeps repository permissions consistent after future Git pushes.
4. Create a Bare Git Repository
A server-side repository should usually be bare. A bare repository stores Git history and references, but it does not contain a checked-out working tree.
Create a new bare repository called project.git:
cd /var/www/git
mkdir project.git
cd project.git
git init --bare --shared=group
Fix ownership and permissions:
sudo chown -R "$USER":gitgroup /var/www/git/project.git
sudo find /var/www/git/project.git -type d -exec chmod 2775 {} \;
sudo find /var/www/git/project.git -type f -exec chmod 664 {} \;
Tell Git that the repository is shared by a group:
git --git-dir=/var/www/git/project.git config core.sharedRepository group
5. Configure Apache Git HTTP Backend
To serve repositories through Apache, create a Git HTTP backend configuration file:
sudo vim /etc/apache2/conf-available/git-http.conf
Add this configuration:
SetEnv GIT_PROJECT_ROOT /var/www/git
SetEnv GIT_HTTP_EXPORT_ALL
SetEnv GIT_HTTP_RECEIVE_PACK 1
ScriptAlias /git/ /usr/lib/git-core/git-http-backend/
<Directory "/usr/lib/git-core">
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
AllowOverride None
Require all granted
</Directory>
Enable the configuration and restart Apache:
sudo a2enconf git-http
sudo systemctl restart apache2
The repository URL will have this form:
https://yourdomain.com/git/project.git
6. Allow Git Push over HTTP or HTTPS
Git clone access and Git push access are different. Clone access uses git-upload-pack. Push access uses git-receive-pack. If you want to push through Apache over HTTP or HTTPS, enable receive-pack for the repository:
git --git-dir=/var/www/git/project.git config http.receivepack true
Restart Apache:
sudo systemctl restart apache2
Do not expose anonymous HTTP push to the public internet. If push over HTTPS is enabled, protect it with authentication, network restrictions, or another access-control method. For private use, SSH is often simpler and safer.
7. Mark the Repository as Safe
If Git is executed by a different user than the repository owner, Git may require the repository to be marked as safe. For an Apache-served repository, add it as a safe directory at the system level:
sudo git config --system --add safe.directory /var/www/git/project.git
To allow all repositories under /var/www/git:
sudo git config --system --add safe.directory '/var/www/git/*'
Restart Apache:
sudo systemctl restart apache2
8. Test Repository Access
Test clone access:
curl -I "https://yourdomain.com/git/project.git/info/refs?service=git-upload-pack"
Test push access:
curl -i "https://yourdomain.com/git/project.git/info/refs?service=git-receive-pack"
If push access is enabled correctly, the response should mention:
application/x-git-receive-pack-advertisement
9. Push a Local Project to the Raspberry Pi
On your local computer, go into your project folder:
cd /path/to/project
If the folder is not already a Git repository:
git init
git add .
git commit -m "Initial commit"
Add the Raspberry Pi repository as the remote:
git remote add origin https://yourdomain.com/git/project.git
Push the current branch. If your branch is called master:
git push -u origin master
If you want to use main instead:
git branch -M main
git push -u origin main
10. Clone the Repository Later
From another machine, clone the repository with:
git clone https://yourdomain.com/git/project.git
Or, if using SSH:
git clone user@yourdomain.com:/var/www/git/project.git
11. Add a Generic .gitignore
Create a .gitignore file:
vim .gitignore
A useful generic version:
# OS files
.DS_Store
Thumbs.db
Desktop.ini
# Editor files
.vscode/
.idea/
*.swp
*.swo
*~
# Logs
*.log
logs/
# Environment and secrets
.env
.env.*
!.env.example
*.pem
*.key
*.crt
secrets/
# Build output
build/
dist/
out/
target/
bin/
obj/
Debug/
Release/
# Python
__pycache__/
*.py[cod]
.venv/
venv/
env/
.pytest_cache/
.mypy_cache/
.ruff_cache/
# C / C++ / CMake
*.o
*.obj
*.a
*.so
*.dylib
*.dll
*.exe
*.out
*.dSYM/
CMakeFiles/
CMakeCache.txt
cmake_install.cmake
compile_commands.json
# Node
node_modules/
.npm/
.yarn/
coverage/
# Databases
*.sqlite
*.sqlite3
*.db
# Temporary files
tmp/
temp/
.cache/
*.tmp
*.bak
*.orig
Add and commit it:
git add .gitignore
git commit -m "Add gitignore"
12. Check Final Permissions
Check the main Git directory:
ls -ld /var/www/git
Check the repository:
ls -ld /var/www/git/project.git
The output should look similar to this:
drwxrwsr-x user gitgroup /var/www/git
drwxrwsr-x user gitgroup /var/www/git/project.git
The important parts are:
owner: your normal server user
group: gitgroup
directory permission: 2775
file permission: 664
Apache access: through gitgroup membership, not ownership
13. Complete Permission Repair Command
If a repository already exists and you want to repair its permissions, use:
sudo groupadd -f gitgroup
sudo usermod -aG gitgroup "$USER"
sudo usermod -aG gitgroup www-data
sudo chown -R "$USER":gitgroup /var/www/git/project.git
sudo find /var/www/git/project.git -type d -exec chmod 2775 {} \;
sudo find /var/www/git/project.git -type f -exec chmod 664 {} \;
git --git-dir=/var/www/git/project.git config core.sharedRepository group
git --git-dir=/var/www/git/project.git config http.receivepack true
sudo git config --system --add safe.directory /var/www/git/project.git
sudo systemctl restart apache2
14. SSH Alternative
Git over HTTPS through Apache works, but SSH is often simpler for private repositories. With SSH, the SSH user writes directly to the bare repository.
Set the remote to SSH:
git remote set-url origin user@yourdomain.com:/var/www/git/project.git
Push the current branch:
git push -u origin master
Or, if your branch is called main:
git push -u origin main
15. Summary
The key idea is to avoid giving the whole repository to Apache. The cleaner permission model is to make the repository owned by your normal server user, assign it to a shared Git group, and add Apache's www-data user to that group only if Apache needs Git access. Directories should use 2775 so new Git files inherit the shared group, and files should generally use 664 so group members can write to them.
Similar posts
comment
There are no comments yet.