This guide covers installing, connecting and running Static Site Push, a free static site generator for WordPress. Use it for the first setup and as a reference for every settings page.
Tip
The plugin's menu in WordPress is called Static Push. Every settings page mentioned below is inside it.
Overview
Static Site Push exports your WordPress site to plain HTML files and publishes them. You keep writing in WordPress; visitors get a fast static copy that has no PHP, database or login page to attack.
- Destinations: a GitHub repository (Cloudflare Pages or any Git-based host builds from it), Cloudflare Pages Direct Upload, or a local folder with ZIP download.
- Automatic pushes: publishing, updating or deleting content pushes only the affected pages. Site-wide changes rebuild everything.
- Static features: search, related posts, feeds, sitemaps, redirects, the theme's 404 page and forms that post to an external service.
- Extras: media offload to Cloudflare R2, HTML clean-up, and Private WordPress mode for a hidden admin site.
Requirements
| Item | Needed |
|---|---|
| WordPress | 5.8 or newer, with pretty permalinks (not "Plain") |
| PHP | 7.4 or newer, with the curl, json, mbstring and zip extensions |
| Loopback requests | The server must be able to request its own pages. Health Check tests this. |
| Destination | A GitHub account, a Cloudflare account (for Cloudflare Pages), or neither for ZIP export |
Install & connect
- Install: in WordPress go to Plugins > Add New, search for Static Site Push, install and activate it.
- Connect GitHub: open Static Push > Destination, choose GitHub and click Connect GitHub. A short code appears. Open github.com/login/device, enter the code and approve. The page updates by itself.
- Choose a repository: pick one from the list, or click New to create a private repository. Use a repository only for the static site; files the export doesn't produce are removed on push (except
.github/, README, LICENSE, CNAME and wrangler configuration). - Set your public address: open Export, choose Full address under Link rewriting and enter your public site address, for example
https://www.example.com. Save. - First push: click Push > Full site. The Dashboard shows live progress.
Keys in wp-config.php
Instead of storing keys in the database you can define them in wp-config.php:
define( 'CFSP_GITHUB_TOKEN', 'github_pat_...' ); // fine-grained token for one repository
define( 'CFSP_API_TOKEN', '...' ); // Cloudflare Pages Direct Upload
define( 'CFSP_ACCOUNT_ID', '...' );
define( 'CFSP_R2_ACCESS_KEY', '...' ); // Media Offload
define( 'CFSP_R2_SECRET_KEY', '...' );
define( 'CFSP_BASIC_AUTH', 'user:password' ); // if WordPress is behind HTTP basic auth
Cloudflare Pages setup
- In Cloudflare go to Workers & Pages > Create > Pages > Connect to Git and pick the repository.
- Framework preset: None. Build command: empty. Output directory:
/(or the folder you set under Destination). - Add your domain under Custom domains.
Static Site Push writes _redirects and _headers for Cloudflare Pages: your own redirects, rules from the Redirection plugin, feed and sitemap redirects, and content types for feeds and stylesheets.
Recommended layout
Move WordPress to its own hostname (for example wp.example.com) and point your public domain at Cloudflare Pages. Then turn on Private WordPress.
Export settings
- Link rewriting: Full address (best for SEO: canonical tags, sitemaps and social previews stay complete) or Root-relative. Rewrite every occurrence also rewrites addresses inside inline CSS, scripts and JSON.
- Page discovery: use the WordPress database to list the home page, posts and pages, categories and tags, authors, pagination, archives, feeds, sitemaps, robots.txt and ads.txt, and the 404 page; optionally follow links between pages.
- Feeds and 404 page: save feeds as
feed.xmlwith a browser stylesheet; the theme's real 404 page is exported and marked noindex. - Sitemaps: add a browser stylesheet to every sitemap, and redirect
/sitemap.xmlto the sitemap index. - Extra and excluded: extra pages, additional files and folders (paths inside the WordPress folder, full server paths or site addresses), and text that excludes an address.
Pushing
| Push option | What it does |
|---|---|
| Pending changes | Exports and pushes only what changed since the last push. |
| Specific pages | Exports and pushes the addresses you list. Add homepage and Add 404 page are one click. |
| Full site | Exports every page again, then pushes. Use it after theme changes or settings that affect every page. |
| Current files | Pushes the export folder as it is, without exporting. |
Only changed files are sent to GitHub, so even a full push usually results in a small commit. A push that would delete most of the repository is refused as a safety measure.
Automation & scheduled posts
- Automatic pushes run after publishing, updating, unpublishing or deleting content, comments, term changes and site-wide changes (menus, widgets, Customizer, theme). A short delay groups quick edits into one push.
- Scheduled posts: WordPress publishes them with WP-Cron, which only runs when someone visits WordPress. Keep Publish missed scheduled posts on, and have a service such as cron-job.org call the
wp-cron.phpaddress shown on the Automation page every minute. - Notifications: optional emails when a push fails or succeeds.
Private WordPress
When WordPress has its own hostname, Stealth > Private WordPress hides it: visitors who aren't logged in see the login page (or a plain "Not found" page), every response is marked X-Robots-Tag: noindex, nofollow, and bots get a robots.txt that blocks everything. Static Site Push itself is let through with a secret key, so exports work exactly as before and the static site stays fully indexable.
Before turning it on
Set the static site address under Export to your public domain first. Files the web server serves directly (images, a robots.txt file on disk) don't pass through WordPress; add a Cloudflare Transform Rule that sets X-Robots-Tag: noindex, nofollow for the WordPress hostname to cover them too. Protect the login page with a login limiter or two-factor login.
Media Offload (R2)
- In Cloudflare, open R2, create a bucket and an R2 API token with Object Read & Write for it.
- In Media Offload, turn it on, enter the account ID, bucket and keys, and click Test connection.
- Set Public media URL to the domain you'll connect to the bucket, for example
https://cdn.example.com. The default key prefixwp-content/uploadskeeps today's paths. - Click Sync media library now. Uploads run in the background and can be paused and resumed.
- Connect the domain to the bucket (R2 > bucket > Settings > Custom Domains), then run a full push.
Skip files named keeps image sizes you don't need out of the bucket (for example *-150x150.jpg); exported pages then use the original image instead, so nothing breaks. Remove skipped files deletes already uploaded files that match the rules. Private folders such as backups and protected downloads are never uploaded.
Site Search
A search index (cfsp-search.json) and a small script are added to the static site. The script takes over your theme's search forms and ?s= links and shows results in an instant overlay, with fuzzy, typo-tolerant matching by Fuse.js (bundled and self-hosted). Choose the content types to index, fuzziness, minimum characters and the number of results. Run a full push after changing search settings.
Related Posts & forms
- Related Posts are worked out at export time from shared tags or categories and written into each post, in the Jetpack Related Posts design, a simple grid or your own template.
- Forms: remove comment forms, or send POST forms to an external form service such as Formspree or Getform.
HTML Clean-up & Stealth
HTML Clean-up removes markup a static site doesn't need: HTML comments, sourceURL markers, block editor and emoji styles and scripts, Dashicons, admin-bar CSS, Yoast schema, speculation rules and preconnect hints you don't use. It can add preconnect and dns-prefetch hints for your media domain, minify HTML, and apply your own removal rules (#element-id, /regex/ or exact text).
Custom removal rules
Each line is a separate rule. Don't paste multi-line blocks: a line such as </script> on its own is ignored for safety, but paste whole blocks as one /regex/s rule or remove them by #id.
Stealth removes the generator tag and WordPress API links, and can strip ?ver= query strings (best left off, because browsers use them to fetch updated CSS and JavaScript).
Feeds, sitemaps & 404
- Feeds are saved as
feed.xml(category and tag feeds too), every link to a feed is updated, and old feed addresses redirect to the new ones. - Feeds and sitemaps get browser stylesheets (
rss-style.xsl,main-sitemap.xsl); paste your own XSL to replace the built-in ones. - The theme's own 404 template is exported as
404.htmland marked noindex; Cloudflare Pages serves it for missing addresses. Re-export it any time with Push > Specific pages > Add 404 page.
Performance
| Setting (Advanced) | Default | Notes |
|---|---|---|
| Pages exported at the same time | 4 | Use 1 on small shared hosting, up to 8 on a dedicated server. |
| Fetch pages directly from this server | On | Skips the round trip through the internet and any proxy. Falls back to the normal route automatically if it doesn't work on your host. |
| Server address | 127.0.0.1 | Enter the server's IP if the web server only listens on it. |
| Batch length / fetch timeout | 20 s / 30 s | Lower values suit hosts that stop long requests. |
Health Check
Health Check tests permalinks, PHP, memory and disk space, loopback requests, the background worker, direct fetch, WP-Cron, scheduled posts, Private WordPress, and the connection to your destination and R2 bucket. Results are kept for an hour; Reset Diagnostics runs every check again. It also shows the IP address your server uses on the internet, which you need if WordPress sits behind a login wall such as Cloudflare Access.
WP-CLI & hooks
wp static-push export [--no-deploy]
wp static-push deploy
wp static-push status
wp static-push cancel
Filters: cfsp_full_urls, cfsp_post_urls, cfsp_exclude_url, cfsp_rewrite_content, cfsp_clean_html, cfsp_fetch_args, cfsp_post_types, cfsp_github_client_id, cfsp_github_protected, cfsp_github_allow_mass_delete.
Actions: cfsp_job_done, cfsp_job_failed, cfsp_settings_updated.
Your data
- Deleting the plugin keeps settings, the GitHub connection, media records and exported files, so reinstalling picks up where you left off. Turn on Tools > Delete all data when the plugin is deleted first if you want everything removed.
- Your static site, GitHub repository and R2 bucket are never touched when the plugin is removed.
- Tools also exports, imports and resets settings (tokens are never included in exports), downloads the site as a ZIP and clears the export folder.
- External services are only contacted for features you set up. The plugin's readme lists each one.
Troubleshooting
| Problem | What to do |
|---|---|
| "Plugin could not be activated because it triggered a fatal error" | Another copy of the plugin is active (for example an older version in a different folder). Deactivate it first. Remove old folders with a file manager rather than the Delete link to keep your settings. |
| "Refusing to delete N files from the repository" | The export folder is almost empty compared with the repository, usually after it was cleared. Run Push > Full site to rebuild it. |
| GitHub API HTTP 401: Bad credentials | The GitHub connection was revoked. Open Destination, click Disconnect and then Connect GitHub again, and use Push > Current files. |
| The push stops with a login-page error | A login wall such as Cloudflare Access is in front of WordPress. Add the server's IP (shown in Health Check) to a bypass rule, or use Private WordPress instead. |
| Images are broken on the static site | Check Media Offload > Public media URL and that the media domain is connected to the R2 bucket. |
| Theme changes don't appear | Theme file edits don't trigger automatic pushes. Use Push > Specific pages for the pages you changed, or Full site. |
| Admin screens look unstyled | A stylesheet failed to load. The plugin retries once by itself; otherwise press Ctrl+F5. |
Need help?
Questions about setup, hosting or a feature request? Contact us on our support page. You can also download the plugin from WordPress.org.