Docs / Migration Guides / Migrate from Netlify to Self-Hosted

Migrate from Netlify to Self-Hosted

By Admin · Mar 15, 2026 · Updated Apr 24, 2026 · 307 views · 3 min read

Why Self-Host Instead of Netlify?

Netlify offers excellent developer experience for static sites and JAMstack applications, but costs can increase with bandwidth, build minutes, and team members. Self-hosting gives you predictable costs, unlimited bandwidth, and full control over your deployment pipeline.

Netlify Feature Replacements

Netlify FeatureSelf-Hosted Alternative
CDNCloudflare (free tier)
Build & DeployGitHub Actions + rsync/Docker
FormsFormspree, or custom API endpoint
FunctionsExpress/Fastify API or Cloudflare Workers
IdentityLogto, Authentik, or Auth0
AnalyticsPlausible or Umami (self-hosted)
Split TestingNginx split_clients module
RedirectsNginx rewrite rules

Step 1: Set Up Nginx for Static Hosting

sudo apt install -y nginx

# Configure virtual host
sudo tee /etc/nginx/sites-available/mysite << CONF
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.html;

    # Handle SPA routing
    location / {
        try_files \$uri \$uri/ \$uri.html /index.html;
    }

    # Custom 404 page
    error_page 404 /404.html;

    # Cache static assets
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
CONF

sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# SSL
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Step 2: Convert _redirects to Nginx

# Netlify _redirects file:
# /old-page /new-page 301
# /api/* https://api.example.com/:splat 200

# Nginx equivalent:
location = /old-page {
    return 301 /new-page;
}

location /api/ {
    proxy_pass https://api.example.com/;
}

Step 3: CI/CD with GitHub Actions

name: Deploy Static Site
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm run build
      - name: Deploy to server
        uses: burnett01/rsync-deployments@7
        with:
          switches: -avz --delete
          path: dist/
          remote_path: /var/www/example.com/
          remote_host: ${{ secrets.SERVER_IP }}
          remote_user: deploy
          remote_key: ${{ secrets.SSH_KEY }}

Step 4: CDN with Cloudflare

# 1. Add your domain to Cloudflare (free plan)
# 2. Update nameservers at your registrar
# 3. Enable proxy (orange cloud) for A records
# 4. Configure caching rules:
#    - Cache Level: Standard
#    - Browser Cache TTL: 1 year for static assets
#    - Always Online: Enabled
# 5. Enable Auto Minify for HTML, CSS, JS

Step 5: Replace Netlify Functions

# Convert serverless functions to Express API
# Netlify function (functions/hello.js):
exports.handler = async (event) => ({
    statusCode: 200,
    body: JSON.stringify({ message: "Hello" })
});

# Express equivalent:
app.get("/api/hello", (req, res) => {
    res.json({ message: "Hello" });
});

# Deploy as a separate service or on the same server

Best Practices

  • Use Cloudflare for free CDN and DDoS protection
  • Set up GitHub Actions for automated deployments on push
  • Configure proper caching headers for optimal performance
  • Use a dedicated deploy user with limited permissions
  • Set up monitoring with Uptime Kuma for availability alerts

Was this article helpful?