You already pay for hosting and a domain. So why are you still opening FileZilla, finding the right folder, dragging files across, and hoping you did not miss one, every single time you change a line of text?

That workflow is slow, and worse, it is unreliable. Files get missed. Old versions get overwritten. Nobody is quite sure what is actually live.

Set this up once and it goes away. You push your code to GitHub, and your live site updates itself. That is the whole promise, and it takes about fifteen minutes to wire up.

The whole setup in 6 minutes 59 seconds. The demo uses HostAfrica with the DirectAdmin control panel. If your host gives you cPanel instead, the labels differ slightly but every step maps across, because the mechanism underneath is plain FTP.

What You Are Actually Building

Three pieces, and none of them are complicated on their own.

An FTP account

Created on your hosting and scoped so it can only reach your web root. Not your control panel login.

Three GitHub secrets

That account's server, username and password, stored encrypted in the repository settings.

A workflow file

One YAML file in your repo that logs in over FTP and syncs your files on every push to main.

After that, a single push is your deploy button.

This works for any static site: plain HTML, or the built output of Astro, Hugo, Eleventy, Jekyll and similar. It is not the right tool for a WordPress site, for a reason worth knowing before you try it, and there is a question about that at the bottom.

Step 1: An FTP Account Scoped to public_html

Jump to 0:24.

In your hosting panel, go to FTP Management, then Create FTP Account. Set a username and a strong password, then, and this is the part that matters, choose the Custom directory option and browse to public_html. See that at 0:44.

Why scope it: these credentials are going to live in GitHub. If the account can only reach public_html, then even in the worst case it can only touch your website files, not your whole hosting account. Do not use your main panel login for this.

When it is created, note the server path it shows you, something like /home/<account>/public_html/. You will not need to type it anywhere, but it confirms the account is rooted where you think it is, and step 4 depends entirely on that.

Step 2: Three Repository Secrets in GitHub

Jump to 1:30.

In your repository, go to Settings, then Secrets and variables, then Actions, then New repository secret. Add three of them.

Secret nameValue
FTP_SERVERftp.yourdomain.co.ke
FTP_USERNAMEThe FTP account you just created, not your panel login
FTP_PASSWORDThe password you set when creating that FTP account

See them being added at 1:58. Secrets are encrypted and masked in the logs, so they never appear in your workflow output. This is the correct place for them. Never put credentials in the workflow file itself, because that file is committed to the repository, and anything committed is in the history for good.

Step 3: Clear the Host's Default index.html

Jump to 2:50. Do this before your first deploy, not after.

Most hosts drop a default index.html or a placeholder page into public_html when the account is created. If yours is still there, it can end up being served instead of your site, and you will spend twenty minutes convinced the deploy failed when it worked perfectly. Clear it out in the file manager first.

Step 4: The Workflow File

Jump to 3:12.

In your project, create the folder .github/workflows, and inside it a file called deploy.yml. Here it is in full.

.github/workflows/deploy.yml
name: Deploy Static HTML to HostAfrica

on:
  push:
    branches:
      - main

jobs:
  deploy:
    name: ๐Ÿš€ Deploy Static Site
    runs-on: ubuntu-latest
    steps:
    - name: ๐Ÿšš Get latest code
      uses: actions/checkout@v4

    - name: ๐Ÿ“‚ Sync HTML files to HostAfrica
      uses: SamKirkland/FTP-Deploy-Action@v4.3.5
      with:
        server: ${{ secrets.FTP_SERVER }}
        username: ${{ secrets.FTP_USERNAME }}
        password: ${{ secrets.FTP_PASSWORD }}
        server-dir: ./

Two lines to check before you commit it.

The branch name

The file above triggers on main. If your repository still uses master, change it, or nothing will ever run, and nothing will tell you why. See it at 3:56.

server-dir, which is the line people get wrong

Jump to 4:16.

It is set to ./ because the FTP account from step 1 is already rooted at public_html. When it logs in, it is standing in the web root, so the destination is "here".

If you set server-dir: public_html/ as well, you get public_html/public_html/ and your site does not appear. If instead you created your FTP account at the hosting account root rather than scoping it, then you do need server-dir: public_html/. Match this one line to how you scoped the account. It accounts for most of the failures people hit with this setup.

Step 5: Commit, Push and Watch It Run

Jump to 4:58. Stage your changes, commit them with a message you will recognise later, and push to the branch named in the workflow.

Your deploy, from now on
git add .
git commit -m "deploy from github to hosting"
git push origin main

Then go to the Actions tab in your repository. The workflow run appears, named after your commit message, showing as In progress. See it at 6:07.

Give it a minute or so. When it completes, refresh your domain and the site is live. The finished result is at 6:42.

From now on that is your entire deployment process. Change a file, commit, push. The site updates itself.

If It Does Not Work

Three things account for most failures, and they are easy to tell apart.

What you seeAlmost certainlyWhat to do
The Action succeeded but the site did not changeserver-dirCheck whether the FTP account is scoped to public_html or to the account root, and match the line to it. Also check the host's placeholder file is gone
The Action failed on loginWrong server, or the wrong usernameConfirm FTP_SERVER is right for your host, and that FTP_USERNAME is the FTP account rather than your control panel username. Those two get mixed up constantly
The workflow never ran at allFile path or branch nameThe file must be at exactly .github/workflows/deploy.yml, and your branch must be the one named in the branches list

The Actions log tells you which of these it is. Open the failed run and read it, because the error is usually in plain English rather than a stack trace.

Security Notes Worth Taking Seriously

While you are tightening things up, if the site is not yet on HTTPS then that matters more than the deployment pipeline does. We have written about moving a site from HTTP to HTTPS separately.

When Cloudflare Pages Is the Better Answer

Worth being honest about this, because it is the same job done with less machinery.

Everything above exists because you are deploying to shared hosting, where FTP is the only door in. If you are not tied to a shared host, platforms like Cloudflare Pages, Netlify and Vercel connect to a GitHub repository directly. No FTP account, no secrets to manage, no server-dir to get wrong, and a free tier that covers a static site comfortably. Our guide to hosting a website on Cloudflare for free walks through that route, and there is a wider comparison of the free hosts too.

So use the FTP method when you have a reason to be on shared hosting: a control panel your team already knows, email on the same account, PHP running alongside the static pages, or a host you have already paid for the year. If none of those apply, connect the repository to a platform that speaks Git natively and skip this entire article.

One thing that does not change either way: the domain and the hosting stay in your own name. WPfoss does not resell either, so whichever route you take you hold the accounts yourself and there is no lock-in.

The Whole Setup in Five Lines

Automatic deployment, start to finish

  1. One FTP account, scoped to public_html, never your panel login.
  2. Three secrets in GitHub: FTP_SERVER, FTP_USERNAME, FTP_PASSWORD.
  3. One deploy.yml at .github/workflows/deploy.yml, naming the branch you actually use.
  4. The host's placeholder index.html cleared out before the first run.
  5. server-dir: ./ when the FTP account is already rooted at the web root.

Then never open FileZilla again.

Frequently Asked Questions

How do I deploy a website from GitHub to shared hosting?

Create an FTP account on your hosting scoped to the web root, store its server, username and password as three GitHub repository secrets, then add a workflow file at .github/workflows/deploy.yml that runs an FTP deploy action on every push to your main branch. After that, pushing to GitHub updates the live site by itself.

Why did my site not change after the Action ran successfully?

Almost always the server-dir line. If your FTP account is already rooted at public_html and you also set server-dir to public_html, the files land in public_html/public_html and nothing appears on the site. The other common cause is the host's placeholder index.html still sitting in the web root and being served ahead of yours.

What should server-dir be set to?

It depends on how you scoped the FTP account, and it must match. If the account is rooted at public_html, use ./ because the login already lands in the web root. If you created the account at the hosting account root instead, use public_html/. Getting this line wrong is the single most common failure.

Is it safe to put FTP credentials in GitHub?

In GitHub Secrets, yes, with one condition. Secrets are encrypted and masked in the logs, so they do not appear in your workflow output. The condition is that you use a dedicated FTP account scoped to the web root, never your main hosting login, so the worst case is limited to your website files. Never write credentials into the workflow file itself, because that file is committed to the repository.

Does this work for a WordPress site?

Not as described here, and it is worth understanding why. The action syncs rather than simply uploading, so what is on the server is made to match what is in the repository. A WordPress site keeps things on the server that are not in your repository, your entire uploads folder among them. Use this for static sites, and use a plugin-based or managed deployment for WordPress.

What if my branch is called master rather than main?

Change the branch name in the workflow file to match. The on.push.branches list decides what triggers a deploy, so if it says main and your branch is master, the workflow will never run at all, with no error to tell you why.

Does GitHub Actions cost anything for this?

For a small static site the usage is a few seconds per deploy, which sits inside GitHub's free allowance. Public repositories and private repositories are billed differently, so check GitHub's current billing page for the exact figures rather than trusting a number in a blog post.

Want this set up properly the first time?

Configured properly means a dedicated scoped credential, a secure protocol, a staging branch, and a rollback path for the deploy that goes wrong at 5pm on a Friday. That is standard work on any site we build or maintain. Send us the repository and tell us who your host is. Talk to us on WhatsApp at +254 722 334 188, email hello@wpfoss.ke, or call +254 709 384 200.

Book a Free Consultation

Related: WordPress Development Kenya ยท Website Maintenance Kenya ยท GitHub Actions docs ยท FTP-Deploy-Action