Running a private Photon instance

Recently, as part of my ongoing quest to self-host as much as possible, I found myself in need of an image proxy. A service I’d installed on an HTTPS-only URL was requesting HTTP-only images, making for a very poor experience.

Were this a WordPress site, I’d have simply installed Jetpack, enabled Photon, and carried on with my day. Beyond it not being a WordPress site, though, this need was for something personal, so I wasn’t particularly motivated to pass URLs to a third party. Fortunately for me, the code behind the server-side portion of Photon is public, because we love open-source at Automattic:

After configuring a new server with nginx, PHP 7, and the necessary image-manipulation libraries, I was ready to install and configure Photon. But first, those libraries:

  • cwebp
  • jpegoptim
  • optipng
  • pngquant

After installing each, you must symlink the binaries to /usr/local/bin or Photon won’t discover them.

After checking out the Photon library from SVN, I was ready to configure nginx. It’s a standard setup, with this location block:

	location / {
		# fastcgi_split_path_info ^(.+.php)(.*)$;
		fastcgi_pass php7-photon;
		fastcgi_index index.php;
		fastcgi_param SCRIPT_FILENAME $document_root/index.php;
		fastcgi_param SERVER_ADDR $server_addr;
		fastcgi_param PHP_VALUE "open_basedir=/var/www/photon/:/tmp/:/dev/shm/:/usr/local/bin/jpegoptim:/usr/local/bin/optipng:/usr/local/bin/pngcrush:/usr/local/bin/pngquant:/usr/local/bin/cwebp";
		include fastcgi_params;

In front of this, I configured a KeyCDN instance, and restricted access to the origin by adding this to my nginx config:

# Restrict access to KeyCDN's origin shield servers, which send the `X-Pull` header
if ( $http_x_pull != 'KeyCDN' ) {
	rewrite ^ $scheme://$request_uri? redirect;

The KeyCDN string is set in the zone configuration.

Lastly, I restrict the origin URLs that my instance will resize, with an exception for the referrer of the service that started this all off. To do so, the following is in my Photon service’s config.php:

// Rebuild Photon URL
$url = sprintf( '%s://%s?%s',
	array_key_exists( 'ssl', $_GET ) ? 'https' : 'http',
	substr( parse_url( 'scheme://host' . $_SERVER['REQUEST_URI'], PHP_URL_PATH ), 1 ), // see (and #66813)

// Don't bother if bad data is passed
$host = parse_url( $url, PHP_URL_HOST );

if ( false === $host ) {
	header( 'HTTP/1.1 400' );
	die( '400 Bad Request' );

// Whitelist requests, with a bypass for certain referers
$referer = parse_url( $_SERVER['HTTP_REFERER'], PHP_URL_HOST );

$hosts_whitelist = array(

if ( PRIVATE_SERVICE_REFERER === $referer ) {
	$hosts_whitelist = array();

// Whitelist file types for redirection
$type = parse_url( $url, PHP_URL_PATH );
$type = pathinfo( $type, PATHINFO_EXTENSION );

$allowed_types = apply_filters( 'allowed_types', array(
) );

// Redirect to the original URL if not whitelisted
if ( ! empty( $hosts_whitelist ) && ! in_array( $host, $hosts_whitelist, true ) ) {
	// Check type before redirecting
	if ( ! in_array( $type, $allowed_types, true ) ) {
		@header( 'HTTP/1.1 400' );
		die( '400 Bad Request' );

	@header( 'HTTP/1.1 302' );
	header( "Location: $url", true, 302 );

Note: Updated 2017-05-05 to limit open redirection by checking extension.

Thanks to the foregoing, I have a private image proxy and resizing service, and I can finally work through the credits I’ve accumulated with KeyCDN. 😂

Using my private instance with WordPress

After completing the above setup, I desired to use my Photon instance for all Jetpack uses too. The following offloads all uses of’s Photon service to my instance.

add_filter( 'jetpack_photon_domain', function() {
	return '';
}, 10, 2 );


If you’ve tried this for yourself, or have questions, let me know in the comments, or at!

One thought on “Running a private Photon instance”

  1. As originally posted, my config.php restrictions created an open redirect. To limit this, I updated the snippet to limit redirection to Photon’s supported post types. It still presents an open redirect, but it’s limited to images, or files masquerading as images.

Leave a Reply