Not every project needs Vercel or Netlify. If you already have shared hosting for an existing site, or if your client is on a budget cPanel plan, you can absolutely host an Astro static site there. It takes a bit of setup, but once the GitHub Action is in place, deploys are fully automated.
Why Shared Hosting Works Fine for Astro Static
Astro's static output is just HTML, CSS, and JavaScript files — no server-side processing required. Any web server that can serve static files (Apache, nginx, even old-school cPanel) handles it perfectly. The limitation is that you can't use Astro's SSR features; everything must be output: 'static'.
Step 1: Configure Astro for Static Output
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
output: 'static',
build: {
assets: '_assets', // keeps assets in a clean subfolder
},
site: 'https://yourdomain.com', // required for sitemap and canonical URLs
});
Step 2: Build and Test Locally
npm run build
npx serve dist/ # preview the built output locally
Browse through your site on localhost:3000. Check that all links work and images load correctly. Internal links should all point to your actual domain structure.
Step 3: Set Up FTP Credentials in GitHub
In your cPanel, create an FTP account with access to your public_html directory (or whichever directory serves your site). Note the host, username, and password.
In your GitHub repository, go to Settings → Secrets → Actions and add:
FTP_HOST— your server's FTP hostnameFTP_USERNAME— the FTP usernameFTP_PASSWORD— the FTP password
Step 4: Create the GitHub Action
# .github/workflows/deploy.yml
name: Deploy to Shared Hosting
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build site
run: npm run build
env:
WP_API_URL: $[[ secrets.WP_API_URL ]]
- name: Deploy via FTP
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
with:
server: $[[ secrets.FTP_HOST ]]
username: $[[ secrets.FTP_USERNAME ]]
password: $[[ secrets.FTP_PASSWORD ]]
local-dir: ./dist/
server-dir: /public_html/
Step 5: Handle .htaccess for Clean URLs
Astro generates /about/index.html for the /about route. Most cPanel servers serve this correctly by default, but if you're getting 404s on direct URL access, add an .htaccess file to your public/ folder:
Options -MultiViews
RewriteEngine On
RewriteCond %[REQUEST_FILENAME] !-f
RewriteCond %[REQUEST_FILENAME] !-d
RewriteRule ^(.*)$ /[1]/index.html [L]
What to Watch Out For
FTP uploads replace files but don't delete removed files. If you rename a page, the old URL stays alive on the server. Either manually clean up old files periodically, or use the dangerous-clean-slate option in the FTP action (this deletes everything and re-uploads — safe if your server-dir only contains your Astro output).
Also: shared hosting has storage quotas. Large image assets should go through a CDN (Cloudflare Images, or just point to WordPress's media library) rather than being bundled into your dist folder.