May 9, 2025
- Add a name
- Leave the remaining options at their defaults unless you have a specific preference (visibility, .gitignore template, license, etc.).
As the repo is created, you will be provided with a full URL that ends with .git. Keep that window open for later use, once we have done a local commit, we can upload it to that origin.
apache or nginx that has access to the www files.# If you are unsure about who owns the www files, run this in your www folder:
ls -la /var/www/html
# Then you can add a user to the right group:
sudo usermod -a -G www-data jan
# Then log out and log back in and run:
id
- The output should reveal that the user is a member of the `www-data` group (or another based on your set up):
sudo find /path/to/your/wordpress/root -type d -exec chmod 775 {} \;
# sudo find /var/www/html/iriskayan.com/ -type d -exec chmod 775 {} \;
sudo find /path/to/your/wordpress/root -type f -exec chmod 664 {} \;
# sudo find /var/www/html/iriskayan.com/ -type f -exec chmod 664 {} \;
# Set it up for future folders
sudo find /path/to/your/wordpress/root -type d -exec chmod g+s {} \;
# sudo find /var/www/html/iriskayan.com/ -type d -exec chmod g+s {} \;
www-data to our user, e.g. jan.sudo chown -R jan:www-data /path/to/your/wordpress/
# sudo chown -R jan:www-data /var/www/html/iriskayan.com/
sudo apt update && sudo apt install git
cd /var/www/your-site-directory
# cd /var/www/html/bachelor-tech.com/
# Set the default branch name as 'main' instead of 'master', as Gitea expects 'main':
git config --global init.defaultBranch main
git init
nano .gitignore
# WordPress
wp-config.php
wp-content/uploads/
wp-content/backups/
wp-content/cache/
wp-content/upgrade/
wp-content/advanced-cache.php
wp-content/wp-cache-config.php
# Used by the Wordfence plugin
wp-content/wflogs/
# Various Logs
*.log
debug.log
# System files
.DS_Store
Thumbs.db
# Gitea webhook
gitea-pull.php
git config --global user.name "Your Name"
git config --global user.email "[email protected]"
# git config --global user.name "Your Name"
# git config --global user.email "[email protected]"
find wp-content/plugins -name ".gitignore"
find wp-content/themes -name ".gitignore"
# If you find some, either remove them or force add them. Examples include:
git add -f wp-content/plugins/complianz-terms-conditions/assets/vendor
git add -f wp-content/plugins/html5-audio-player/vendor
git add -f wp-content/plugins/unyson-subsolar/framework/extensions/shortcodes
cd /var/www/html/website
# cd /var/www/html/bachelor-tech.com
git add .
# In case you get a warning because some plugin already uses a .git folder, then remove it:
# rm -rf wp-content/plugins/penci-shortcodes/pagespeed/vendor/sabberworm/php-css-parser/.git
# Commit it:
git commit -m "Initial commit of WordPress site"
git remote add origin https://gitea.bachelor-tech.com/jan/bachelor-tech.com.git
git push -u origin main
git branch -m master main
mkdir /var/www/git-pulls
nano /var/www/git-pulls/pull-mywebsite.com.sh
#!/bin/bash
# A script to pull the latest changes and fix permissions afterwards.
# Exit immediately if any command fails
set -e
# --- Configuration ---
SITE_DIR="/var/www/html/your-folder"
REPO_OWNER="www-data:www-data"
# --- End Configuration ---
echo "Starting deployment for iriskayan.com..."
# Navigate to the site directory
cd "$SITE_DIR"
# Pull the latest changes
echo "Pulling latest changes from Git..."
git pull
# Enforce correct ownership and permissions
echo "Fixing permissions..."
sudo chown -R "$REPO_OWNER" .
sudo find . -type d -exec chmod 775 {} \;
sudo find . -type f -exec chmod 664 {} \;
sudo find . -type d -exec chmod g+s {} \;
echo "----------------------------------------"
echo "Deployment and permission fix complete."
# Make it executable
sudo chmod +x /var/www/git-pulls/pull-mywebsite.com.sh
# Create a symlink (you will be asked to provide your sudo password
sudo ln -s /var/www/html/.local/state/pull-mywebsite.sh /usr/local/bin/pull-mywebsite
# sudo ln -s /var/www/html/.local/state/pull-iriskayan.sh /usr/local/bin/pull-iriskayan
pull-mywebsite
web1 to gitea. In the following example, we have web2 that we want to connect to download the data from gitea (and if there is any content in that same folder location, then move it away).web2) just in case, since we will need to flush the website data out./tmp path + proceed with permission changes.# Move web files away on web2 (if you cloned the VM before, for example)
sudo mv /var/www/html/your-website /tmp/
mkdir /var/www/html/your-website
# Add permissions of your web server (such as for apache or nginx)
cd /var/www/html/your-website
sudo usermod -a -G www-data your_user
# sudo usermod -a -G www-data jan
# Set ownership on the empty directory first
sudo chown your_ssh_user:www-data /var/www/html/your-website
# sudo chown jan:www-data /var/www/html/my-website
# Install git if not done already
sudo apt install git -y
# Enter the folder you created earlier and clone the repo
cd your-website
# Clone it to the same folder (hence the dot - otherwise a sub-folder is created!)
git clone https://gitea.bachelor-tech.com/your-gitea-user/my-wordpress-site.git .
.gitignore file!/tmp folder, you will need to move it back. For example:sudo mv /tmp/your-website/wp-config.php /var/www/html/your-website
sudo mv /tmp/your-website/wp-content/uploads /var/www/html/your-website/wp-content/.
# Ensure you are in your web hosting directory:
cd /var/www/html/your-website
# Set file permissions - wait for a bit for each to complete (based on file amount)
sudo chown your_ssh_user:www-data /var/www/html/your-website
sudo find . -type d -exec chmod 775 {} \;
sudo find . -type f -exec chmod 664 {} \;
sudo find . -type d -exec chmod g+s {} \;
# Copy an existing config from another virtual
sudo cp /etc/nginx/conf.d/existing-website /etc/nginx/conf.d/new-website
# sudo cp /etc/nginx/conf.d/bachelor-tech.com.conf /etc/nginx/conf.d/learn-english.uk.conf
# Replace domain name with the new one
sudo sed -i 's/old-website.com/new-website.com/g' /etc/nginx/conf.d/existing-website
# sudo sed -i 's/bachelor-tech.com/learn-english.uk/g' /etc/nginx/conf.d/learn-english.uk.conf
# Check the config for syntax & reload it:
sudo nginx -t
sudo systemctl reload nginx
wp-config.php file and sync the wp-content/uploads folder, such as by using syncthing or lsyncd. Check out my other guide for that.crontab -e
*/10 * * * * cd /var/www/html/your-site-directory && git pull origin main > /dev/null 2>&1
main). That is what we will be focusing on in this section below.web1.yourdomain.tld to HAProxy. We do not want to interfere with the regular HTTPS traffic on the general HTTPS front-end that you use for outside traffic. It would be good to create a virtual IP interface that we can then use on HAProxy.
web1.yourdomain.tld host. Assuming you are already using Unbound on OPNsense (or another similar service), you will need to set it up.Similarly, in the 'Rules & Checks' section, go to 'Rules' and create a new rule.
Test type: IF
Select conditions: your previously created one
Execute function: Use specified Backend Pool
Use backend pool: your previously created pool, such as backend_web1
# On Web1 VM:
sudo nano /etc/nginx/conf.d/bachelor-tech.com.conf
# Change:
server_name bachelor-tech.com;
# to
server_name bachelor-tech.com web1.bachelor-tech.com;
# Save and exit the text editor
# Reload nginx:
sudo systemctl reload nginx
openssl s_client -connect 192.168.8.99:443 -servername your.domain.tld.https://web1.bachelor-tech.com/gitea-pull.php).POSTmain$secret variable in a PHP script in the next step. This provides basic security on the endpoint.# Create the file with a user that the web server has access to or change ownership afterwards.
nano /var/html/www/your-website-folder/gitea-pull.php
<?php
// VARIABLE DEFINITIONS
// Secret Key (must match the one in Gitea)
$secret = 'your-long-random-webhook-secret'; // Make sure this matches your Gitea secret
// Repo path
$repo_path = '/var/www/html/bachelor-tech.com';
// Log file
$log_file = '/var/log/gitea-deploy.log'; // Make sure this file is writable by www-data
// Define SSH key location
$ssh_key_path = '/var/www/.ssh/id_ed25519'; // Ensure that www-data can reach it
// REST OF THE SCRIPT
$date_today = date('[Y-m-d H:i:s]');
file_put_contents($log_file, '-----------------\nPull initiated on ' . gethostname() . ' on ' . $date_today . '\n', FILE_APPEND);
// Signature Verification
$hub_signature = '';
if (isset($_SERVER['HTTP_X_HUB_SIGNATURE_256'])) {
$hub_signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'];
} else {
http_response_code(400); die('Security Error: GitHub-compatible signature header not found.');
}
$parts = explode('=', $hub_signature, 2);
if (count($parts) !== 2) {
http_response_code(400); die('Security Error: Invalid signature format.');
}
list($algo, $hash) = $parts;
$payload = file_get_contents('php://input');
$payload_hash = hash_hmac($algo, $payload, $secret);
if (!hash_equals($hash, $payload_hash)) {
http_response_code(403); die('Security Error: Signature verification failed.');
}
// Construct the SSH command
$git_ssh_command = "ssh -i {$ssh_key_path} -o IdentitiesOnly=yes -o StrictHostKeyChecking=no";
// The command to be executed
$command = "cd {$repo_path} && GIT_SSH_COMMAND='{$git_ssh_command}' git pull origin main 2>&1";
// Execute the command
$output = shell_exec($command);
// Log the output
$log_entry = date('[Y-m-d H:i:s]') . " --- \n" . $output . "\n";
file_put_contents($log_file, $log_entry, FILE_APPEND);
// Respond to Gitea
http_response_code(200);
echo "Deployment successful. See log for details.\n";
echo $output . '\n';
?>
# Log file creation and file permissions
sudo touch /var/log/gitea-deploy.log
sudo chown www-data:www-data /var/log/gitea-deploy.log
# Change ownership of the script to www-data, so that it a
sudo chown www-data:www-data gitea-pull.php
# Add the repo into exceptions to accept data from other autors (such as www-data when you do updates).
sudo -u www-data git config --global --add safe.directory /var/www/html/bachelor-tech.com
# Log into Gitea.
sudo nano /etc/gitea/app.ini
# Add a line about webhook and add any relevant subnet (perhaps only one is needed):
[webhook]
ALLOWED_HOST_LIST = 192.168.1.0/24,192.168.2.0/24
# Save an exit and restart gitea:
sudo systemctl restart gitea
web1) and create a key. Log in as a user that has permissions to access the hosted files.# Create a key
ssh-keygen -t ed25519 -C "webhook-gitea@web1"
# Accept the default path for now.
# Do not create a passphrase (press enter twice).
# Create a folder and set permissions to be reachable by www-data (or your PHP-FPM user):
sudo mkdir /var/www/.ssh
sudo mv /home/$USER/.ssh/id_ed25519 /var/www/.ssh
sudo chown www-data:www-data /var/www/.ssh
# Print out the public side of the key:
cat /home/$USER/.ssh/id_ed25519.pub
cd /var/www/html/your-website
# Use the git.yourdomain.tld alias that we created earilier in Unbound.
git remote set-url origin [email protected]:user/repo.git
# git remote set-url origin [email protected]:jan/learn-english.uk.git
# git remote set-url origin [email protected]:jan/bachelor-tech.com.git
# Perform the first fetch manually to confirm the key trust
git fetch origin
ssh -v [email protected] instead. Ensure the hostname resolves to the correct local IP of Gitea, not the virtual IP on OPNSense.web1_deploy_key.webhook can only call allowed HTTP servers', it means the web server is not properly allow listed in Gitea's config or you forgot to restart the gitea service after you added it.gitea-pull.php script started to run, but then it crashed for some reason. Most often, this is because there is no password-less login available. Create an SSH key and add it into Gitea.mysqldump to export the specific database.
mysql -u root -p
SHOW DATABASES;
# If you need to find out what users have access to the DB (not necessarily to which database):
SELECT grantee, group_concat(privilege_type) from information_schema.user_privileges group by grantee;
# Then you can enquire for a specific user
SHOW GRANTS FOR 'user'@'localhost';
# My case:
# SHOW GRANTS FOR 'bachelor_tech_com_wp_user'@'192.168.%';
- Run the following command to export the DB (if you are on a DB cluster such as MariaDB's Galera, then run it from any of the members):
# SPECIFIC DB export: Use root or another user with the required privileges
mariadb-dump --user=root -p --lock-tables --extended-insert --databases your_database_name > /var/backups/your_database_name.sql
# ALL DBs export (into one file, though).
mariadb-dump --user=root -p --lock-tables --extended-insert --all-databases > /var/backups/dbs_alldatabases.sql
# My case for each
mariadb-dump --user=root -p --lock-tables --extended-insert --databases bachelor_tech_com_db > /var/backups/bachelor_tech_com_db.sql
mariadb-dump --user=root -p --lock-tables --extended-insert --databases learn_english_db > /var/backups/learn_english_db.sql
wp-cli - if you do not have it, see the install steps. Then proceed as follows:# Go into the webhosting folder to update
cd /var/www/html/your-website
# Firstly update the CLI, if needed
wp cli check-update
# Check WP version
wp core version
# Update WP core
wp core update
# Update all plugins
wp plugin update --all
# Update themes
wp theme update -all
git add .
# NOTE: If you get an error related to a php file in wp-content/wflogs,
# then it means that that folder was not listed in .gitignore before the data were
# committed. So make sure the path is added into .gitignore if not done already and
# then run the following:
# Confirm it was there before
# git ls-files | grep "wp-content/wflogs"
# Flush it away
# git rm --cached -r wp-content/wflogs/
git commit -m "Updated the theme, plugins and WP core to 6.8.3."
# In case you get an error related to 'web1_deploy_key is not authorized to write to',
# the perms need to be fixed.
git push origin main
wp core version or checking if a plugin version is up to date.
cd /var/www/html/your-website
git status
# This tells Git to discard all local changes and reset the files to a specific state.
git reset --hard origin/main
# Remove untracked files recursively as a dry run
git clean -n -d
# Once you are comfortable with it, clean it
git clean -f -d
Warning: This will flush away files that are not in Gitea, such as your wp-config.php file as well as anything in the wp-content/uploads folder!
Now you can either run git pull manually or make another little change on web1 and watch the magic.
Issue 2: If the git status command does not show any differences even though you know there are some on Gitea, then most likely, the SSH key is not reachable for the user running the command. Make sure it is accessible, especially to whoever is running the PHP FPM module, which is typically www-data. That is why it is good to have it under /var/www/.ssh rather than your own home directory.
Issue 3: The .log file reveals the following error: "fatal: detected dubious ownership in repository at '/var/www/your-path'.
sudo -u www-data git config --global --add safe.directory /var/www/html/your-websitewp-config.php file - it is sensitive and should be kept in a more password-sensitive space such as a secure note in Bitwarden or another password manager.wp-content/uploads folder - this one changes frequently and we want a more dynamic service to sync it in real time between nodes. Check out my guide on Syncthing to handle this one./wp-content/uploads folder to be synced in some other way (such as via lsyncd or syncthing), you will need to reconfigure it. In this case, we will check how to go about it with syncthing.Enjoying this tutorial?
This site is a non-profit project. If it saved you some time, you can support it by getting Jan some coffee.
☕ Buy me a coffee
Comments