How to Set Up a WordPress Plugin Update Server

How to Set Up a WordPress Plugin Update Server

AT
AutomagicWP Team

If you build WordPress plugins for clients or sell them commercially, you've run into this problem. WordPress.org handles updates automatically for hosted plugins, but the moment you're distributing something privately, you're on your own.

That means manually emailing ZIP files, logging into twenty different wp-admins, or building your own update infrastructure. None of those are particularly good options.

This guide covers how WordPress's update mechanism works, the open-source DIY route, and when a managed service makes more sense.

How WordPress plugin updates work

WordPress checks for plugin updates by sending a POST request to https://api.wordpress.org/plugins/update-check/1.1/. The payload is a JSON object listing every active plugin, including its slug and current version.

The API responds with a list of plugins that have newer versions available. If your plugin's slug appears in that response, WordPress shows the update notification in wp-admin.

WordPress also lets you intercept this entirely. There's a filter called pre_set_site_transient_update_plugins that fires before WordPress stores the response. Hook into it, inject your own version data, and WordPress will treat your server as the source of truth instead.

Here's the minimal PHP to redirect update checks for your plugin:

	add_filter( 'pre_set_site_transient_update_plugins', function( $transient ) {
    if ( empty( $transient->checked ) ) {
        return $transient;
    }
 
    $plugin_slug = 'your-plugin/your-plugin.php';
 
    // Fetch latest version info from your update server
    $response = wp_remote_get( 'https://your-update-server.com/plugins/your-plugin/info.json' );
 
    if ( is_wp_error( $response ) ) {
        return $transient;
    }
 
    $data = json_decode( wp_remote_retrieve_body( $response ) );
 
    if ( isset( $data->version ) && version_compare( $data->version, $transient->checked[ $plugin_slug ], '>' ) ) {
        $transient->response[ $plugin_slug ] = $data;
    }
 
    return $transient;
} );

That covers the client side. The harder part is the server: what's actually sitting at https://your-update-server.com.

The Update URI header (WordPress 5.8+)

The approach above works, but hooking pre_set_site_transient_update_plugins globally is fragile — you're intercepting every update check on the site, and conflicts with other plugins doing the same thing are common.

WordPress 5.8 introduced a cleaner mechanism: the Update URI plugin header. Add it to your plugin's main file and WordPress knows exactly where updates come from, without touching the global transient filter.

	/**
 * Plugin Name:  My Plugin
 * Version:      1.0.0
 * Update URI:   https://your-update-server.com/plugins/my-plugin/
 */

WordPress extracts the hostname from that URI and fires a corresponding filter: update_plugins_your-update-server.com. Your server-side code hooks into that specific filter rather than the global one, which avoids conflicts and makes the update source explicit and auditable.

	add_filter( 'update_plugins_your-update-server.com', function( $update, $plugin_data, $plugin_file, $locales ) {
    $response = wp_remote_get( 'https://your-update-server.com/plugins/my-plugin/info.json' );
 
    if ( is_wp_error( $response ) ) {
        return $update;
    }
 
    $data = json_decode( wp_remote_retrieve_body( $response ) );
 
    if ( version_compare( $data->version, $plugin_data['Version'], '>' ) ) {
        return (object) [
            'slug'    => $plugin_data['TextDomain'],
            'version' => $data->version,
            'url'     => $data->url,
            'package' => $data->download_url,
        ];
    }
 
    return $update;
}, 10, 4 );

There's also a useful edge case: Update URI: false disables update checks for a plugin entirely. For internal client builds that should never show an update notification, this is the right call.

WordPress 6.1 extended the same mechanism to themes via a Update URI theme header.

This is the approach AutomagicWP uses under the hood. The client library sets the Update URI header to AutomagicWP's domain and hooks into the matching update_plugins_automagicwp.com filter. From WordPress core's perspective it's indistinguishable from a WordPress.org update.

Option 1: Host your own update server with wp-update-server

The most widely used open-source solution is wp-update-server by Yahnis Elsts, the same developer who built plugin-update-checker. The two libraries work together: the server hosts your plugin ZIPs and serves version metadata, the client library handles the WordPress-side integration.

To get started you need a PHP server (a basic VPS or shared hosting works), the wp-update-server package installed, and your plugin ZIPs in the /packages directory. There's also an optional license key system if you want to gate updates.

For a single plugin, the initial setup is genuinely simple. Configure a few PHP constants, upload your ZIPs, done.

The problems come later. Running your own update server means owning the infrastructure, which is fine until a client calls at 2am because their site is throwing an update failure. You're also responsible for keeping the server up through traffic spikes, applying security patches to the server itself, and still manually uploading a new ZIP every time you ship. There's no built-in release history, no changelog hosting, no plugin metadata storage.

For developers with one or two plugins who like having full control, this works well. For agencies managing six plugins across thirty client sites, the maintenance load compounds.

Option 2: Use the WordPress.org plugin repository

This is only an option if your plugin is free or freemium. WordPress.org requires GPL licensing and open-source code; it won't accept private or commercial-only plugins.

Around 60,000 plugins are listed on WordPress.org. The update mechanism is baked into WordPress core, the repository hosts everything, and there's no server to run. If your plugin qualifies, it's the lowest-friction option available.

If you've built something for a single client or you're selling it commercially outside of WordPress.org, the repository isn't available to you. It's a public directory.

Option 3: Freemius

Freemius is the established platform for commercial WordPress plugins. It handles payments, licensing, update delivery, upgrade prompts, and usage analytics. If you're running a plugin business with public pricing and multiple customers, it covers a lot of ground.

Freemius takes 7% of revenue and the entire system is built around their monetization model. It's designed for selling plugins publicly, not for pushing private builds to a specific set of client sites.

Option 4: A managed update service

AutomagicWP sits in the gap between rolling your own server and using Freemius. The scope is narrower: upload a release, connected sites update automatically, no server to manage.

The workflow:

  1. Create a plugin in your dashboard
  2. Add the AutomagicWP client to your plugin (an API key and a small PHP library)
  3. Upload a ZIP to publish a new version
  4. Every connected site checks for updates on the standard WordPress schedule and pulls it down

For agencies distributing custom plugins to client sites, this fits the actual problem better than Freemius does. No payment processing, no public storefront, just update distribution.

It includes version history so you can download any previous release, changelogs attached to each release (surfaced in the WordPress update UI), plugin metadata like icons and banners, and a GitHub Actions integration that deploys a release automatically when you push a git tag. You commit code, push v1.2.3, and connected sites start pulling the update without anyone touching a dashboard.

Setting up automatic updates with GitHub Actions

If you're already using GitHub, this is worth setting up regardless of which update server you use. It removes the manual packaging and uploading step.

For AutomagicWP, the workflow looks like this:

	# .github/workflows/release.yml
name: Release
 
on:
  push:
    tags:
      - 'v*'
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Build plugin
        run: |
          composer install --no-dev --optimize-autoloader
          zip -r my-plugin.zip . --exclude="*.git*" --exclude="tests/*"
 
      - name: Deploy to AutomagicWP
        uses: automagicwp/deploy-action@v1
        with:
          api-key: ${{ secrets.AUTOMAGICWP_API_KEY }}
          plugin-id: ${{ secrets.AUTOMAGICWP_PLUGIN_ID }}
          file: my-plugin.zip

Push a tag and the release goes out. Shipping a plugin update stops being a task you put on your calendar.

Which approach fits your situation

wp-update-server is a good fit if you have one or two plugins, you want full control over hosting, and you're comfortable maintaining a PHP server long-term.

WordPress.org is the obvious choice for free, GPL-licensed plugins. Zero infrastructure, built-in update delivery. If your plugin qualifies, there's no reason not to use it.

Freemius makes sense if you're building a commercial plugin business with public pricing and you need payment processing and licensing handled alongside updates. The 7% cut is the price of not building that yourself.

A managed update service is worth considering if you're an agency pushing custom plugins to client sites and you'd rather not run a server or wire up CI/CD manually.


The WordPress plugin market is worth over $1 billion annually, and around 100 million active plugin installs come from outside WordPress.org. Private distribution is a real and common need, and the tooling options have improved significantly over the last few years.

The client-side integration is the same regardless of which server you use: hook into WordPress's update transient, point it at your endpoint, and the built-in update UI handles the rest.

Frequently asked questions

Do I need a PHP server to distribute plugin updates outside WordPress.org?

Not if you're using a managed service. If you go with wp-update-server, yes, you'll need a PHP host.

Can I keep update history so clients can roll back to older versions?

The open-source solution doesn't include this; you'd keep old ZIPs on hand manually. Managed services typically include versioned release history with downloads.

How does WordPress know when to check for updates?

WordPress checks automatically every 12 hours via the wp_update_plugins cron event. It also runs a check when you visit Dashboard > Updates or the Plugins page.

Can I use GitHub as my update server directly?

Yes. With plugin-update-checker you can point at a GitHub releases page. GitHub isn't designed for this though, and rate limits become a problem at scale. For personal projects it's fine.

What's the difference between an update server and a license server?

An update server distributes plugin files and version metadata. A license server checks whether a given site is authorized to receive updates. They're separate systems and you can run one without the other, though most commercial plugins need both.

Share this article