4 min read

Static sites are fast, secure, and cheap to run. You build them locally, then push the output to your server. This guide covers deploying to both shared hosting and VPS.

Prerequisites

  • A static site generator (Hugo, Astro, Jekyll, or plain HTML)
  • Your site built locally (e.g., a dist/, public/, or _site/ folder)
  • An FTP client (shared hosting) or SSH access (VPS)

On Shared Hosting

Option A: Upload via File Manager

  1. Build your site locally
  2. Zip the output folder
  3. Log into cPanel → File Manager
  4. Navigate to public_html/ (or your domain’s document root)
  5. Upload the zip
  6. Extract it
  7. Make sure index.html is in the root, not inside a subfolder

Option B: FTP Upload

Shared and WordPress hosting plans have no shell access, so there is no SSH or SFTP. Use FTP with TLS instead, in a client like FileZilla. Create an FTP account in cPanel > FTP Accounts first (or use your cPanel login):

SettingValue
ProtocolFTP, with Require explicit FTP over TLS (FTPS)
HostYour domain or server IP
Port21
UsernameYour FTP account (e.g., [email protected])
PasswordThat account’s password

Upload the contents of your build folder to public_html/.

This is the cleanest workflow: push to Git, and the server pulls and deploys automatically.

Step 1: Set Up the Server

  # Install Git and Nginx
sudo apt install git nginx -y

# Create a web root
sudo mkdir -p /var/www/mysite
sudo chown $USER:$USER /var/www/mysite
  

Step 2: Create a Bare Git Repository

  # This is the repository your local machine pushes to
mkdir -p ~/repos/mysite.git
cd ~/repos/mysite.git
git init --bare
  

Step 3: Add a Post-Receive Hook

This hook automatically deploys your site whenever you push:

  nano ~/repos/mysite.git/hooks/post-receive
  

Add:

  #!/bin/bash
GIT_WORK_TREE=/var/www/mysite git checkout -f main
  

Make it executable:

  chmod +x ~/repos/mysite.git/hooks/post-receive
  

Step 4: Configure Nginx

The hook checks out the whole repository, source included, so point Nginx at the build folder inside it. Use public for Hugo, dist for Astro, or _site for Jekyll:

  sudo nano /etc/nginx/sites-available/mysite
  
  server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    root /var/www/mysite/public;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    # Cache static assets
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}
  
  sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
  

Step 5: Push from Your Local Machine

On your local machine, add the server as a Git remote:

  cd your-project

# Build first
npm run build   # or hugo, or jekyll build

# Set up the remote
# Use the same user that created the bare repository in Step 2
git remote add deploy ssh://youruser@your-server-ip/home/youruser/repos/mysite.git

# Push
git push deploy main
  

Build-Then-Push Workflow

For static site generators, the typical workflow is:

  1. Edit content locally
  2. Build: npm run build / hugo / jekyll build
  3. Commit the output folder
  4. Push: git push deploy main

Adding SSL

After your site is live, add HTTPS:

  sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
  

Certbot will auto-renew the certificate. Test it:

  sudo certbot renew --dry-run
  

Troubleshooting

ProblemFix
Push rejectedMake sure you’re pushing to the correct branch (main vs master)
403 ForbiddenCheck file permissions: 644 for files, 755 for directories
Site shows Nginx default pageVerify the server_name in your Nginx config matches your domain
CSS/JS not loading, or 403 on the homepageCheck that root in Nginx points at the build folder (public, dist, or _site) and that the build output is committed. Clear the browser cache
Hook not firingVerify the hook is executable: chmod +x post-receive

What to Do Next

Last updated 21 Oct 2026, 00:00 +0300. history

Was this page helpful?