How to host a WordPress plugin on my own server and provide updates?

All Credit to the following post goes to Abid Omar. The full tutorial can be found on Tuts+: A Guide to the WordPress HTTP API: Automatic Plugin Updates

View Plugin On Github


The gist is to create a new class which will post an external file ( API ) and return plugin to download if it’s out of date. The below assumes you’re creating a plugin using the following code ( not in functions.php or mu-plugins directory ).

  1. wp_autoupdate.php will be an external file ( in your plugin ) holding the Auto Update class defined below
  2. update.php is an external “remote” file used to check for update, creates the API.

The below will be our initial setup.

add_action( 'init', 'wptuts_activate_au' );
function wptuts_activate_au()
{
    require_once ('wp_autoupdate.php');      // File which contains the Class below
    $wptuts_plugin_current_version = '1.0';
    $wptuts_plugin_remote_path="http://localhost/update.php";
    $wptuts_plugin_slug            = plugin_basename(__FILE__);
    new wp_auto_update( $wptuts_plugin_current_version, $wptuts_plugin_remote_path, $wptuts_plugin_slug );
}

Autoupdate Class – wp_autoupdate.php

class wp_auto_update
{
    /**
     * The plugin current version
     * @var string
     */
    public $current_version;

    /**
     * The plugin remote update path
     * @var string
     */
    public $update_path;

    /**
     * Plugin Slug (plugin_directory/plugin_file.php)
     * @var string
     */
    public $plugin_slug;

    /**
     * Plugin name (plugin_file)
     * @var string
     */
    public $slug;

    /**
     * Initialize a new instance of the WordPress Auto-Update class
     * @param string $current_version
     * @param string $update_path
     * @param string $plugin_slug
     */
    function __construct( $current_version, $update_path, $plugin_slug )
    {
        // Set the class public variables
        $this->current_version = $current_version;
        $this->update_path     = $update_path;
        $this->plugin_slug     = $plugin_slug;
        list ($t1, $t2)        = explode("https://wordpress.stackexchange.com/", $plugin_slug);
        $this->slug            = str_replace( '.php', '', $t2 );

        // define the alternative API for updating checking
        add_filter( 'pre_set_site_transient_update_plugins', array( &$this, 'check_update' ) );

        // Define the alternative response for information checking
        add_filter('plugins_api', array(&$this, 'check_info'), 10, 3);
    }

    /**
     * Add our self-hosted autoupdate plugin to the filter transient
     *
     * @param $transient
     * @return object $ transient
     */
    public function check_update( $transient )
    {
        if( empty( $transient->checked ) ) {
            return $transient;
        }

        // Get the remote version
        $remote_version = $this->getRemote_version();

        // If a newer version is available, add the update
        if ( version_compare( $this->current_version, $remote_version, '<' ) ) {
            $obj          = new stdClass();
            $obj->slug    = $this->slug;
            $obj->new_version = $remote_version;
            $obj->url     = $this->update_path;
            $obj->package = $this->update_path;
            $transient->response[$this->plugin_slug] = $obj;
        }
        var_dump( $transient );
        return $transient;
    }

    /**
     * Add our self-hosted description to the filter
     *
     * @param boolean $false
     * @param array $action
     * @param object $arg
     * @return bool|object
     */
    public function check_info( $false, $action, $arg )
    {
        if( $arg->slug === $this->slug ) {
            $information = $this->getRemote_information();
            return $information;
        }
        return false;
    }

    /**
     * Return the remote version
     * @return string $remote_version
     */
    public function getRemote_version()
    {
        $request = wp_remote_post( $this->update_path, array( 'body' => array( 'action' => 'version' ) ) );
        if( ! is_wp_error( $request ) || wp_remote_retrieve_response_code( $request ) === 200 ) {
            return $request['body'];
        }
        return false;
    }

    /**
     * Get information about the remote version
     * @return bool|object
     */
    public function getRemote_information()
    {
        $request = wp_remote_post( $this->update_path, array( 'body' => array( 'action' => 'info' ) ) );
        if( ! is_wp_error( $request ) || wp_remote_retrieve_response_code( $request ) === 200) {
            return unserialize( $request['body'] );
        }
        return false;
    }

    /**
     * Return the status of the plugin licensing
     * @return boolean $remote_license
     */
    public function getRemote_license()
    {
        $request = wp_remote_post( $this->update_path, array( 'body' => array( 'action' => 'license' ) ) );
        if( ! is_wp_error( $request ) || wp_remote_retrieve_response_code( $request ) === 200 ) {
            return $request['body'];
        }
        return false;
    }
}

Remote ( External ) Update API – update.php

if( isset( $_POST['action'] ) ) {
  switch( $_POST['action'] ) {
    case 'version':
      echo '1.1';
        break;

    case 'info':
      $obj              = new stdClass();
      $obj->slug        = 'plugin.php';
      $obj->plugin_name="plugin.php";
      $obj->new_version = '1.1';
      $obj->requires="3.0";
      $obj->tested      = '3.3.1';
      $obj->downloaded  = 12540;
      $obj->last_updated = '2012-01-12';
      $obj->sections    = array(
        'description'     => 'The new version of the Auto-Update plugin',
        'another_section' => 'This is another section',
        'changelog'       => 'Some new features'
      );
      $obj->download_link = 'http://localhost/update.php';
      echo serialize( $obj );

    case 'license':
      echo 'false';
        break;
  }
} else {
    header( 'Cache-Control: public' );
    header( 'Content-Description: File Transfer' );
    header( 'Content-Type: application/zip' );
    readfile( 'update.zip' );
}

update.php will keep the most up-to-date information on your plugin. Using the init function you would pass the currently installed version so that it can post it to update.php and check against the current it has. There’s some room for improvement on the code to make it more streamlined but I used this to do exactly ( sorta ) what you’re trying to do so it’s a good jumping point.