VIP Go IP Ranges

In some situations, it might be necessary to whitelist requests from your VIP Go environment to private resources. For example, you may need to whitelist requests from your VIP Go site to an internal tool that’s behind a firewall.

We provide publicly accessible IP ranges for our origin data centers for these situations.

The IP range for each data center can be found here.

Whitelisting the provided IP range for a given data center will allow your site to communicate with a restricted platform/resource.

Precautions Around IP Range Updates

VIP Go IP ranges are subject to change at any time, and we do not provide advance warning of IP range updates.

For this reason, we recommend building your application in such a way that unexpected IP range updates are non-breaking either because the ranges aren’t required for vital site functionality, a fallback method is provided, or some other strategy.

There are a few mechanisms to monitor updates to the published ranges:

  • The serial JSON field within the response contains an epoch timestamp of the last change,
  • The updatedAt JSON field within the response contains an ISO 8601 timestamp of the last change,
  • HTTP response of requests made with the If-Modified-Since header. If the last modification date/time is newer than the value provided in the If-Modified-Since request header, the response will be HTTP 200 OK. Otherwise, the response will be HTTP 304 Not Modified.

Managing User Access

We encourage all VIP clients to manage user access to their websites and GitHub repositories. It’s best to designate primary administrators who will be in charge of adding and removing users. We recommend at least one primary administrator for each client, two would be even better in case one of them is unavailable. Primary administrators should feel empowered to add additional administrators as they see fit while following best practices.

Managing GitHub Access

A collaborator with admin privileges in your GitHub repository will be able to add or remove additional collaborators. Find instructions in GitHub’s Documentation. There are a few points to keep in mind when deciding on permissions for collaborators:

  • Please only add users with read or write permissions as required. Users who would need write access include those who will be committing code to the repository.
  • Admin collaborators can force push (which is blocked in VIP Go environments) after removing restrictions. If they do this, they should restore restrictions afterwards.
  • Please review the full description of every permission level in GitHub’s documentation.

Add a User to Your VIP Go Site

As an Administrator, you are able to add users to your website through wp-admin (also known as the WordPress Dashboard). Add users to your site using the default WordPress user roles, or customized roles unique to your business needs:

  1. Log into your VIP Go site to access wp-admin.
  2. From the sidebar, click on Users.
  3. Next click the Add New button to add a new user to your site.
  4. Fill out the form and select the role that your user should be assigned.
    • Check the Send User Notification option if you want the user to receive an email with a password-set link. If you do not select this option, the user will need to access the login URL (example.com/wp-admin) and use the password reset feature to generate a password.
  5. That’s it! You did it!

Add a User to Your VIP Go Multisite

In a multisite, user management is done at the network (top) level. Only a Super Admin can add users to the network. Once users are added at the network level, Administrators for individual subsites can then add any of these existing users as required.

If you’d like to add a user to your VIP Go multisite as a Super Admin, follow the steps outlined here:

  1. Log into your VIP Go multisite to access wp-admin.
  2. In the admin bar, at the top, hover over My Sites > Network Admin > Users and then click on Users. Adding a user to a multisite through network admin
  3. Next, click the Add New button to add a new user to your site.
  4. Fill out the form. Note this will only prompt you for a username and email address. A password reset link will be sent to the user via email by default.
  5. Now that you’ve added the user, you will be able to edit the user profile. Click Edit user at the top of the screen. (You can also go back to the Users list in Network Admin and hover over the user.)
  6. Check the box that says Grant this user super admin privileges for the Network.
  7. Scroll down to the end of this page and click Update User. Now the user has Super Admin privileges for the entire multisite.
  8. Note, if you ever need to delete this user, you must first remove the Super Admin privileges. To do that, go into the user’s profile, unselect the super admin privileges option, and then update.

X-hacker and X-Powered-By HTTP Headers

By default, VIP adds two custom HTTP response headers to every application we host. These headers help us monitor our platform and can be useful when troubleshooting the origin of a request, but if required they can be removed.

HTTP headers are not visible when viewing web pages in a browser, neither are they visible when viewing the HTML source for a web page. HTTP headers are part of the HTTP protocol used to request web pages and request responses from API endpoints, and also to send the response, e.g. the web page or the API response.

HTTP headers added by our platform, along with all other request and response headers, can be inspected by savvy users using specific tools, e.g. cURL. Here is an example of the X-hacker and X-Powered-By HTTP headers added by our platform:

X-hacker: If you’re reading this, you should visit wpvip.com/careers and apply to join the fun, mention this header.
X-Powered-By: WordPress.com VIP <https://wpvip.com>

How to change or remove the headers

To alter the headers, use the wp_headers filter to unset or modify them as desired. The source code contains the latest header keys and can be used as a reference.

The following snippet can be used to remove the X-hacker header:

add_filter( 'wp_headers', function( $headers ) {
    if ( isset( $headers['X-hacker'] ) ) {
        unset( $headers['X-hacker'] );
    }
    return $headers;
}, 999 );

To change the value of a header, replace the value with a new one. For example:

add_filter( 'wp_headers', function( $headers ) {
   $headers['X-hacker'] = 'Follow the white rabbit over to wpvip.com/careers to join our team.';
   $headers['X-Powered-By'] = 'WordPress VIP, an Automattic Production.';
    return $headers;
}, 999 );

These two snippets can also be mixed and matched as needed.

Launch day playbook

This playbook outlines the considerations, from VIP’s experience, to ensure a smooth and successful launch. Please note that the points below are a guide only, and the VIP team will advise on the specifics of each launch.

Scheduling

After the project has been kicked off with VIP, we schedule launches as outlined below:

  • VIP requires that all launches are scheduled at least 5 business days in advance. We’ll need to get the date and time of the launch on our calendar to ensure that a Technical Account Engineer (TAE) and a dedicated engineer are on hand.
  • VIP supports launches Monday to Thursday, 9am-8pm UTC. If a launch is requested outside these hours, we require a minimum of 10 business days’ notice to arrange for appropriate resources. Please keep in mind that an alternative date and time may need to be arranged, based on availability and the complexity of the launch.
  • If a launch needs to be rescheduled, the same notice period applies for the rescheduled date. For example, if a launch that was booked for tomorrow needs to be rescheduled, it will need to move at least 5 business days in the future.

Launch steps

  • At the scheduled time and date, everyone will join a StormChat (a single-use live text chat room).
  • VIP will be expecting to update the site to the production domain at that time, with the goal of having the site live within a specified time period (typically, 30 minutes – 1 hour).
  • For a typical launch, the site’s primary domain will be set, followed by QA and then switching DNS.
  • If the site is not live by the end of that time period, we recommend rescheduling the launch and addressing any outstanding issues via Zendesk support tickets.
  • If more extensive QA is required, VIP may recommend that the site’s primary domain is set at a scheduled time, with the change being notified in Zendesk. Then, all parties will convene on a StormChat for the DNS switch. There is also the option to use Maintenance Mode or another form of access restriction if a soft-launch is desired.

Expected launch duration

  • Additional complexities such as those listed below can be expected to add time to the launch:
  • When planning the launch, a TAE will outline the specific launch steps and expected duration.

Pre-launch checklist

  • A site needs to be free of issues and performing well at least 2 business days before the scheduled launch. If there are outstanding issues, or bugs are still being worked on, within 2 business days before the launch, VIP will need to reschedule the launch.
  • If a final database and/or media import is required before the launch, it should usually be performed at least 3 hours before the launch steps are performed. The exact timing will be advised by a TAE and will be informed by the duration of the initial import. If an editorial freeze is not possible during those 3 hours, any new content can be double-posted, or it can be imported post-launch.
  • One week prior to launch, the DNS records’ TTL (Time To Live) should be lowered as far as possible.

Node.js Applications on VIP Go

The VIP Platform has first-class support for hosting Node.js (Node) applications.

Types of Applications

We currently support two types of Node.js applications on VIP.

Decoupled / Headless Frontend

The REST API allows WordPress to operate in a decoupled / headless manner. Rather than a traditional PHP-driven theme, sites can be run as a static single-page application (SPA) or a fully isomorphic application (with server-side rendering).

(Companion) Microservices

WordPress is flexible enough to support many different use cases, but not all of them. A companion microservice can be used to offload tasks that may not be well-suited for a typical WordPress app, such as a proxy for another API or service or a middleware layer like a GraphQL API.

Preparing your Application

Requirements

For your Node.js application to work on the VIP Platform, it must adhere to the following requirements.

You can automate these checks as part of your CI process by running the following command on the application root directory:

npx @automattic/vip-go-preflight-checks

Note: that npm 5.2 or higher is required for the preflight checks package.

Use the @automattic/vip-go package

Our helper package simplifies some of the boilerplate and makes it easier to integrate with platform features like logging. It currently handles the following:

  • Starts an HTTP server listening on process.env.PORT, which is dynamically assigned to each container.
  • Responds to GET /cache-healthcheck? with a 200 status code, which is used by our monitoring systems to verify the health of your application.
  • It also includes helpers for logging and integrating New Relic.
Use a CI Service

If your application requires any build processes like transpiling, compiling, etc. you must use a CI service (like CircleCI or GitHub Actions) to generate a built copy of the application.

The service should transpile, compile, and verify a build of your app and then push that to a “built” branch. Learn more about setting up Automated Build & Deploy.

Included npm scripts

On VIP Go, every time you push a new change to your application, we pull the latest code from the deployment branch and then run the commands below. Your application must respond with a non-error exit code.

  • npm install --production
    • This is triggered to install any production dependencies needed for the app.
    • All required production dependencies must added to package.json, including any packages that were not included in the built copy. Note that devDependencies are not installed.
  • npm run build
    • This is triggered to run any final build steps.
    • Any additional build steps that were not completed via CI should happen here.  If your app doesn’t need an additional build step, feel free to just echo something (e.g. echo "No build needed; skipping...")
  • npm start
    • This is triggered to boot up the app.
    • This should boot up the HTTP server.

If any of these commands are missing or fail, the application will not work correctly.

Stateless and Multi-container

All applications on VIP Go must be able to run across multiple containers and processes at the same time.

This means that the app must largely be stateless. If any data is stored in memory it will be lost on deploy or if containers are added/removed. If data needs to be shared across containers/processes or you need guaranteed persistence, you can use a data store like MariaDB or Redis, which we can enable for your app.

No Process Monitors

For production deployment, your application does not need any process monitors (like nodemon, PM2) to handle restarts. This is handled automatically by the VIP platform.

Recommendations

We also recommend following these best practices:

  • Keep the “dependencies” list small and lean.
    • Node.js containers will run npm install --production every time the app is deployed or a new container is added. This can take a significant amount of time if you’re using a large number of dependencies and slow down deployment time as a result.
  • Use npm audit or a service like Renovate or Dependabot to automatically keep your dependencies up-to-date, especially for security updates.

Creating your Application

A member of the VIP team will work with you to get your application up-and-running in our environment.

You’ll want to make sure that your application is set up according to the Requirements and Recommendations noted above.

We’ll also need the following details:

  • Domain name.
  • GitHub users.
  • The version of Node.js you’re hoping to use.
    • We currently support 6.x, 8.x, and 10.x
  • Any environment variables that should be added.
  • Any data stores you’ll need alongside the application.
    • We currently support MariaDB and Redis.

More about your Node.js Application

Logging

For logs generated by the application, the @automattic/vip-go package provides a wrapper around Winston, a popular logging library. Using this library will ensure that logs are accessible by the VIP team which will allow us to better support your team in case of issues (and will be directly accessible to your team in the near future). If you’d like to log to your logging system of choice, Winston has adapters for many popular logging services and APIs such as Sentry.

Using Redis

If using Redis for your application, you need to use the following env variables to create the connection:

  • REDIS_MASTER: This will contain the IP of the master host alongside the PORT. Before using it, make sure to split it with ‘:’ to get the IP and the port.
  • REDIS_PASSWORD: This will contain the password of the Redis containers. This password is automatically generated and passed as an environment variable by the VIP Platform.

This will be simplified in a future version of the @automattic/vip-go package.

WP-CLI

WP-CLI is a powerful and extensible way to interact with WordPress from the command line. WP-CLI is supported on VIP as a beta feature via our VIP-CLI tool.

Most things you can do with WordPress core have an equivalent command in WP-CLI:

Getting Started

To get started, first install the latest version of VIP CLI:

npm i -g @automattic/vip

Interactive Shell

The easiest way to use WP-CLI is to use the interactive shell mode provided by VIP CLI. This feature provides a terminal-like interface into your WordPress site:

vip @my-site -- wp

my-site.production:~$ wp option get home
https://example.com
my-site.production:~$ wp cache delete my-cache-key my-cache-group
Success: Object deleted.
my-site.production:~$ 

Using the interactive shell mode is a convenient way to run multiple commands on the same environment and behaves much like a standard terminal or SSH session would.

Direct Commands

WP-CLI commands can also be run directly without the interactive shell mode, just like any normal command:

vip @my-site.production -- wp option get home
vip @my-site.staging -- wp post list --posts_per_page=100 --url=example.com/fr
vip @my-site.develop -- wp cache delete some-key

Running commands directly allows the output to be redirected and piped for creating powerful workflows and tools:

vip @my-site.develop -- wp user list --format=json | jq
vip @my-site.staging -- wp term list category --format=csv > category.csv

Note: In the examples above, the double dash (--) before wp separates arguments of the vip command from those of the wp command. You should always include them to avoid unexpected issues due to parameter conflicts.

Audit Log

For greater visibility into how WP-CLI is being used, the VIP Dashboard includes a log of all WP-CLI commands run on your applications and environments. To view the log, log in to the Dashboard and select “WP-CLI” when viewing an application.

Allowed Commands

As WP-CLI support is still in beta, only built-in commands are supported at this time. In the near future, we’ll be opening access to custom commands.

Here is the current list of supported commands:

wp cache add
wp cache decr
wp cache delete
wp cache get
wp cache incr
wp cache replace
wp cache set
wp cache type
wp cap (all)
wp cli version
wp comment approve
wp comment count
wp comment create
wp comment delete
wp comment exists
wp comment get
wp comment list
wp comment meta
wp comment recount
wp comment spam
wp comment status
wp comment trash
wp comment unapprove
wp comment unspam
wp comment untrash
wp comment update
wp core version
wp cron (all)
wp embed (all)
wp help (all)
wp language core activate
wp language core is-installed
wp language core list
wp language plugin is-installed
wp language plugin list
wp language theme is-installed
wp language theme list
wp media image-size
wp menu (all)
wp network (all)
wp option (all)
wp plugin activate
wp plugin deactivate
wp plugin get
wp plugin is-active
wp plugin is-installed
wp plugin list
wp plugin path
wp plugin search
wp plugin status
wp plugin toggle
wp plugin verify-checksums
wp post create
wp post delete
wp post exists
wp post get
wp post list
wp post meta
wp post term
wp post update
wp post-type (all)
wp rewrite (all)
wp role (all)
wp sidebar (all)
wp site activate
wp site archive
wp site create
wp site deactivate
wp site delete
wp site list
wp site mature
wp site meta
wp site option
wp site private
wp site public
wp site spam
wp site switch-language
wp site unarchive
wp site unmature
wp site unspam
wp super-admin (all)
wp taxonomy (all)
wp term create
wp term delete
wp term get
wp term list
wp term meta
wp term recount
wp term update
wp theme activate
wp theme disable
wp theme enable
wp theme get
wp theme is-active
wp theme is-installed
wp theme list
wp theme mod
wp theme path
wp theme search
wp theme status
wp transient (all)
wp user add-cap
wp user add-role
wp user check-password
wp user create
wp user delete
wp user get
wp user list
wp user list-caps
wp user meta
wp user remove-cap
wp user remove-role
wp user reset-password
wp user session
wp user set-role
wp user spam
wp user term
wp user unspam
wp user update
wp widget (all)
wp akismet check
wp akismet recheck_queue
wp akismet stats
wp jetpack get_stats
wp jetpack module
wp jetpack options
wp jetpack publicize
wp jetpack sitemap
wp jetpack status
wp jetpack test-connection

Two-factor Authentication on VIP Go

Two-factor authentication (also known as multi-factor authentication and 2fa) is a method of securing accounts which requires a user to know something (e.g. a password) but also that you possess something (e.g. your mobile device). This method of requiring multiple forms of verification is an easy to way to protect your sites against common account breaches due to leaked or guessed passwords. Two-factor authentication is integrated with all WordPress sites on the VIP Platform and easy to use and enforce.

Enabling Two-Factor Authentication

If you have a WordPress account, to enable Two-factor authentication, just visit Users > Your Profile and enable your preferred authentication methods in the Two-Factor Options section.

Enforcing Two-factor Authentication

Two-factor authentication is required on VIP Go for all administrators and custom roles with the manage_options capability. If you’d like to force two factor authentication for other roles, you can use the wpcom_vip_is_two_factor_forced filter. For example, to enable for all users that can edit posts:

add_action( 'set_current_user', function() { 
    $limited = current_user_can( 'edit_posts' );
    add_filter( 'wpcom_vip_is_two_factor_forced', function() use ( $limited ) {
        return $limited;
    }, PHP_INT_MAX );
} );

Or, to enable for all users on the site:

add_filter( 'wpcom_vip_is_two_factor_forced', '__return_true' );

Disable Two-factor Authentication Enforcement

If you’re using an external auth provider that already enforces two-factor authentication, you can choose disable enforcement for users on the site. You can add this to a file inside your client-mu-plugins folder. (Note that with this snippet, the built-in two factor options will still be available to users).

add_filter( 'wpcom_vip_is_two_factor_forced', '__return_false' );

If you’d like to remove the built-in Two-factor options completely, you can add the following snippet to a file inside your client-mu-plugins folder:

add_filter( 'wpcom_vip_enable_two_factor', '__return_false' );

Resetting Two-Factor Authentication for Locked Out Users

There are two primary methods available for both admin and super admin user roles to disable two-factor authentication for users that are locked out of their account.

Prior to disabling two-factor authentication, we highly recommend confirming that the user has indeed lost access to their account. Since emails can be faked, we recommend confirming with the individual in person or over the phone.

To disable two-factor authentication, you can do either of the following from the Dashboard under Users > Edit > Two-Factor Options:

  • Deselect all available two-factor methods. This will allow the user to login without needing any additional code.
  • Enable the Backup Codes option. Then, you can send a backup code to the user that they can use to login to their account.

Once the user regains access to the account, they can adjust any two-factor settings to prevent losing access moving forward (reset phone number, for example). We also recommend having them print out backup codes to prevent future lockouts.

The VIP Cache Personalization API

The VIP Cache Personalization API provides an easy way to customize the caching behavior of the VIP CDN, allowing personalization and segmentation of your audience without sacrificing stability and performance. The VIP CDN is a global network of servers owned and managed by VIP, and amongst other services, it provides caching for the VIP Platform.

What is Cache Personalization?

Web development is often about balances and compromises. When it comes to serving dynamic content within a CMS, there are two ends of the spectrum:

  1. Generate each page every time a user request comes. This is great for ensuring the user always has the most up-to-date copy but comes with a performance penalty as the code and database calls run each time.
  2. Generate a page once and then cache the content for future requests. This option offers fantastic performance but comes with the challenge of requests potentially displaying outdated content until it is regenerated manually or automatically

The VIP Platform has been designed to handle both. However, to achieve the high levels of scaling and performance that many applications expect, caching is essential. By serving the same cached content to as many users as possible, we can deliver on the promise of a platform that scales with your needs.

The default VIP CDN configuration works for most cases, but is insufficient when more control of the caching behavior is required (for example, serving different content to different groups of users). The VIP Cache Personalization API can help with that. It provides an easy way to customize the caching behavior of the VIP CDN while still leveraging the power and scale that it provides.

When should you use it?

Some common uses for the VIP Cache Personalization API include:

  • Tailoring content to the user’s location
  • Displaying different website features to a select group (e.g. Beta site)
  • A/B testing
  • Gating content until users opt-in (e.g. Accepting Terms and Conditions)
  • Content paywalls
  • Membership sites using custom authentication systems
  • 3rd party integrations that require uncached content/resources

How does it work?

We provide a variety of ways to segment your content at the edge, serving different audience segments different content from different “cache buckets”. This is achieved through the Cache Personalization API. The API includes three implementations that can be taken depending on use case:

Audience Segmentation

This is useful when you want to serve different cached content to different constituencies in your audience.The API provides an easy way to create a new cache group and assign users to specific segments within that group.

For example, if we were introducing new Beta features for our site and wanted to allow users to opt-in to those features, we would create a new group called “beta” with two user segments within that: users that have opted into the beta and users that have not.

You can define as many groups as you want and users can belong to multiple groups and segments as well. It’s important to note that the higher the number of groups, the higher the variance in caching responses, which can result in decreased cache efficacy and performance. As such, the API should be used with care.

At a technical level, when a user is assigned to an audience segment, the API sets a cookie defining the group or groups the user is part of.

Encrypted Audience Segmentation

Encrypted audience segmentation is similar to “audience segmentation”, but ensures that it is not possible for even for a very savvy technical user to assign themselves to change the segments they have been assigned to. Typical use cases for this are paywalls, authenticated content, and any other situations where you want to cache content based on encrypted credentials not visible to the user.

At a technical level, the cookie defining the group or groups the user is part of contains an encrypted string which cannot be read or altered by the user without scrambling the content.

Please note that encrypted audience segmentation requires activation by the VIP team; get in touch if you’d like to learn more or get started using this feature.

Cache Bypass (i.e. Disable Caching)

This gives you the ability to turn off all caching for a given visitor. This is useful for 3rd party services that consume data or content from your application and require uncached data. It’s also applicable when working with external authentication system. Learn more about the implementation.

Please note that disabling caching can have detrimental effects on the performance of your site. We’d be happy to help validate and verify your use cases before you implement them; please reach out if that’s of interest.

How do you use it?

We’ve put together some useful tutorials and examples that walk through how to implement various uses cases using the API.

If you have have any questions about the API or how to make it work with of one of your use cases, please get in touch and we’d be happy to help.

Testing your site

When you test your site, we recommend you run all tests on your production environment. Walk through your site using all the functionality of your dashboard, including plugins that you and your team will use on a regular basis. This could include creating test posts and widgets.

In addition to testing backend functionality, you’ll want to look at the frontend to ensure it functions as expected. If anything appears broken or is not working as expected, you might want to take a deeper look at your output and see which errors or warnings need to be addressed.

At a minimum, we recommend the following  tests:

  • Create a post as a user with the “editor” role
  • Create a post as a user with the “author” role
  • Upload an image to the media library
  • Edit a post
  • Delete a post
  • Create a new user
  • Delete a user
  • Change a user’s role
  • Add a widget
  • Modify a widget
  • Verify settings are correct for external services like Google Analytics, Twitter, Facebook, etc.
  • Any features of your editorial workflow that rely on plugins or theme functionality
  • 301 redirects, if any, still work

Single Sign On

Single Sign On (SSO, not to be confused with Jetpack SSO) is possible for clients using any identity provider (IdP) that supports SAML (or Security Assertion Markup Language). We do not support other SSO technologies at this time. We also cannot install any middleware required in some Shibboleth configurations. Most IdPs can support SAML.

Setting up the IdP

SAML IdP’s require you to register the VIP Go application as a service provider. They have different ways of approaching this but the purpose is to:

  • Set up the application as a legitimate service provider.
  • Tell the IdP where and how to communicate with your VIP Go application.
  • Generate the certificate and URLs the IdP will use to send and encrypt communication with the VIP Go application.

Most IdPs have an application creation here’s the documentation for creating custom applications on major IdPs:

You will need:

  • The ACS location, usually example.com/wp-login.php/?saml_acs (where example.com is your domain)
  • The entity-id: php-saml

Once you create your SAML application, the IdP will provide the following:

  • Entity ID (a unique URL)
  • Single Sign-on URL
  • X.509 Certificate to setup WordPress.

Setting up WordPress

In order for our team to continue to provide support for your application, we have the following requirements:

  • You must configure your SSO plugin to create local user accounts
  • If you force SSO for all users, you must provide a way for support users to circumvent the SSO flow on login
  • If you force SSO on all pages of the site, you must expose the XML-RPC endpoints to Jetpack requests.

To allow users to circumvent the SSO flow, the easiest way is to provide a url parameter like wp-login.php?normal that directs users to the wp-login form. A more secure way is to detect if a user is accessing the site through VIP’s proxy servers.

Use one of these plugins:

  • OneLogin’s WordPress SAML
  • Human Made’s WordPress Simple SAML

Onelogin’s WordPress SAML plugin

OneLogin’s WordPress SAML plugin works with any IdP and is managed through a settings page where you can fully configure your application. If you’re using this plugin, make sure you also have our helper plugin installed to your client-mu-plugins directory which takes care of some of the required details above and also ensuring cookies and other SSO settings pass through our cache layers.

Options and Settings

You can mostly choose how to configure your own SSO. Some settings may be dictated by your IdP. If you’re doing a lot of custom configuration, we highly recommend you thoroughly test your SSO setup on your VIP Go application before launch.

Here are our recommended settings (these are under the “Options” heading of the OneLogin plugin):

  • Create user if not exists: This causes WordPress to create local accounts for users that sign in over SSO. Required
  • Update user data: This causes user attributes like first name, last name, and email address to change on WordPress when they change in your IdP. Recommended
  • Single Log Out: only useful if the client’s IdP supports it. Not recommended
  • Alternative ACS Endpoint: Not supported
  • Match WordPress account by: You can choose how to match your users to their IdP accounts.

There are many additional options and settings. For the most part, you shouldn’t need to change these unless your IdP requires it.

WordPress Simple SAML plugin

Human Made’s WordPress Simple SAML plugin also works with any IdP but stores the SAML configuration in code and facilitates SAML without extra settings screens. Because of how Human Made approached this and how our platform works, we require some extra code in your theme’s functions.php file. If you need help generating this code, reach out, and we’ll provide the code for use with this plugin. The helper code handles configuring the IdP and mapping your roles. Your developers will want to take a close look at this before launch. Loading the SAML configuration from an XML file provided by your IdP is currently not supported on VIP Go.

Notes on role mapping

Sometimes the role sent by an IdP doesn’t match a role in the WordPress install. If this is the case, you have three options for resolving the mismatch. Any users without a matching role will be assigned the default, usually “Subscriber.”

  • Create roles in your WordPress application that match your IdP.
  • Create roles in your IdP that match roles in WordPress.
  • Map your IdP’s roles to existing roles in WordPress. You do not need to map every role, and more than one role can be mapped to a given WordPress role.

Preventing unauthenticated site access with SSO

The only way to make a VIP Go site “private” is by requiring SSO authentication to access any page on your entire site. To do this, use the OneLogin plugin and enable the “Force SAML Login” option. You must still provide a method for VIP Support to circumvent SSO to access the site.

Requiring SSO to login

We require the creation of local accounts on the WordPress install so that we can more easily troubleshoot when users are having problems. This doesn’t prevent the client from requiring SSO to log in. If the client requires SSO for all logins from their users, enable the following options in the OneLogin plugin’s settings:

  • Prevent reset password: This will prevent users from resetting their WordPress account passwords.
  • Prevent change password: This will prevent users from changing their WordPress password.
  • Prevent change mail: This will prevent users from changing the email address in their WordPress account profile.

QA Recommendations

We have a few recommendations for clients to test their SSO configuration before launch.

Check users

  • Create test users within the IdP, create one for each role that mapped to WordPress to make sure users have the right role when they sign in.
  • Test any known role conflicts to make sure they are resolved as you expected.
  • Test whether users can successfully log in and out without affecting other SSO sessions in their organization

Test content protections

  • If the entire site requires authentication, make sure clients verify by anonymously access the site
  • Make sure all login requests go through the single sign-on process.

Retiring sites from VIP Go

When you wish to retire a site from VIP Go, we ask that you notify the VIP team via a support ticket 30 days in advance of the site retirement date. At the end of this period, we will normally retire the site(s) along with the non-production environment(s) and update your account’s billing details as per our agreement.

In the case of selective retirement of subsites within a Multisite, we will delete/archive the requested sites and production monitoring for these subsites sites will be disabled.

In order to archive or migrate the site to other hosting, you will need several elements, and we cover these below.

Codebase / GitHub repository

You may wish to first read  about the structure of your VIP Go codebase.

If the GitHub repository will no longer be used by an active VIP Go site, once the site(s) have been retired, the associated GitHub repository will be removed as well so please ensure you have a local copy of the repository saved. You can either download a copy of the code in your VIP Go repository as it is, clone a copy of the Git repository, to retain the version history and all changes, or we can transfer the repository to your GitHub account, to retain all version history, GitHub issues, GitHub pages, etc.

Download a copy of your codebase

  1. Navigate to your VIP Go repository on GitHub
  2. Select “Clone or Download” from the front page of your repository
  3. Select “Download ZIP”
  4. Check the contents, and save the file

Clone your repository

GitHub maintains documentation on cloning a GitHub repository.

Transfer a repository

If you would like to transfer your VIP Go repository to your GitHub account, please contact us; read more about repository transfers.

Files and data

If you would like an archive of all files that have been uploaded to your site, please contact us. The archive will be supplied with the same directory structure as the file uploads. The data will be supplied as SQL from the mysqldump client.

The VIP code analysis bot

When you create a Pull Request (PR) on a VIP client site repository in the VIP GitHub organization (https://github.com/wpcomvip/), you may see a review from our automated VIP Code Analysis bot. The bot’s GitHub username is wpcomvip-vipgoci-bot, and you can see their GitHub user account.

What does the bot do?

The bot runs our PHP_CodeSniffer (PHPCS) ruleset with the WordPress-VIP-Go standards, and also the PHP linter.

The PHP linter will highlight code syntax and compilation errors, these are usually fatal and so need to be addressed before the code is deployed.

PHPCS is a tool that will help you write VIP approved code by ensuring it meets VIP coding standards. The rules are designed to identify where code will not work, and to help you follow VIP best practices for writing secure, performant, and future-friendly code. We recommend you install the PHPCS and the ruleset locally by following the documentation here, and this will give you even more immediate feedback as you develop.

The bot works by adding a GitHub PR review (or reviews) detailing the feedback based on our ruleset. A review with no issues will show no interaction from the bot, whereas if the bot finds either PHPCS or PHP linting errors the bot will comment on and review the PR:

What should I do about the bot feedback?

You should review the feedback from the bot, and decide how to act on it. You have several choices for each item of feedback:

  • Address the feedback by amending your code
  • Choose to ignore the feedback, and add code comments to instruct the bot to ignore the issue (see below)
  • Dismiss the review issue via the GitHub PR interface

Many issues will be correct and should be addressed, but as with all automated feedback there will be some wrongly flagged issues that it is safe to ignore (false positives). There may also be some issues that the bot feedback misses (false negatives). We aim to reduce false positives and false negatives over time, please feel free to open a ticket if you find such and we’ll be happy to discuss.

If you have any concerns about a review added by this bot, please open a ticket and we’ll be happy to help.

Code comments to ignore PHPCS feedback

You can configure the bot to ignore certain parts of the when running the PHPCS ruleset as described in “Ignoring Parts of a File” from the PHPCS documentation. Here’s an example:

// Ignore a single line:
$xmlPackage = new XMLPackage; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase -- reasons

// Disable the a check for multiple lines, and then enable all checks at the end:
// phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase -- reasons
$xmlPackage['error_code'] = get_default_error_code_value();
$xmlPackage->send();
// phpcs:enable

Read the PHPCS documentation for other methods to have the scanner ignore portions of a file or whole files.

Interpreting your PHPCS report

When a code base is ready for automated scanning, the repository is checked by PHPCS scan using the WordPress-VIP-Go ruleset.  The initial scan will include a report that categorizes the scan’s results based on the severity of the errors and warnings the scan found.

Errors with severity level 6 and above

An ERROR with severity level 6 and above may indicates code that may not function on VIP Go. This could be due to:

While some may be false positives, not addressing the valid ones in this category will likely result in a loss of functionality.

We don’t recommend fixing these errors found in third-party code like plugins and themes. Instead, consider looking for an alternative that provides the same functionality. If there isn’t an alternative that meets your needs, consider whether you truly need the code and thoroughly test its functionality if you do.

Errors at severity level 5

Code that triggers an ERROR with severity level 5 may have issues such as (but not limited to):

Especially with escaping errors, there may be false positives. The only way to know for sure is by inspecting these lines further. Errors at this level expose the site to security and performance problems.

Warnings at severity 6 and above

Code that triggers a WARNING with severity level 6 and above may expose the site to performance and security problems. This includes (but not limited to):

  • Custom Database Tables
  • Using $wpdb directly
  • Using wp_mail()
  • User provided data not properly sanitized

Warnings at severity 6 and above are addressed to prevent poor performance and security vulnerabilities.

Warnings at severity level 5

Code that triggers a WARNING with severity level 5 may cause problems in certain circumstances, such as high traffic events. This warning level includes issues such as (but not limited to):

  • Uncached functions
  • Functions with poor performance
  • Database queries with poor performance
  • Using strip_tags instead of wp_kses

VIP recommends that warnings at severity level 5 are addressed.

Warnings at severity level 4 and under

WARNINGs with severity level 4 and under are triggered when the code is not adhering to VIP’s recommended best practices. This includes issues such as:

  • Including files without a full path
  • Using loose comparisons
  • Having an undefined variable
  • Not enqueuing scripts

These warnings will be included in the report. Addressing them will help keep the code base clean and prevent unexpected bugs or side effects.

VIP Go glossary

  • Application: the application is delivered by your software codebase, you might also refer to this as the “website”, or “site”
  • Environment: your application can run in a variety of environments, eg: production or develop (or “local development”)
  • Environment ID: the ID number assigned to an environment, this can be discovered using the VIP CLI tool

How code is deployed on VIP

Each production environment tracks the master branch of a GitHub repository (access to this repo will be provided after the kickoff call). Child environments track different branches of the repo, and auto-deploy.

Each site repo utilises a webhook on GitHub that is triggered by pushing code to a branch. The GitHub webhook notifies the VIP Platform that new code should be deployed, the VIP Platform determines which applications and environments the code should be deployed to (if any). The VIP Platform then updates the code available to each affected environment. The deployment process generally takes less than 10 seconds from code push to completed deployment.

Deployments and code review

For sites on Full or Enhanced code review levels, the master branch will auto-deploy to the environment until the initial code review is complete. After that point, deployments to master will follow the GitHub Pull Request workflow.

For sites not receiving manual code review, the master branch will always auto-deploy to the environment. VIP recommends always following a PR workflow to enable the VIP code analysis bot to provide automated feedback.

Automated build and deploy process

VIP’s automated build and deploy process can automatically transpile/concatenate/minify/optimize your JavaScript, CSS, and static assets (almost anything except PHP) and deploy it your site, using a Continuous Integration (CI) or Continuous Delivery (CD) service like Travis CI or CircleCI.

This means the working branch can remain clean — with only source files — and the CI/CD service can manage the build and deployment process for you.

Please note that the automated build and deploy process is not available for all clients. If in doubt, please open a ticket with the VIP team for further advice.

Further reading

Multisite on VIP Go

As well as single site WordPress installations on VIP Go, we also support multisite (also known as WordPress Networks). A WordPress multisite allows you to run multiple WordPress sites from the same WordPress installation, using the same set of user accounts. One common use case for a WordPress multisite is to support multiple languages, using the Multilingual Press plugin from Inpsyde, one of our VIP Featured Partner Agencies.

This help document covers the specifics of WordPress multisites on VIP Go, if you need a more general primer, you can read the WordPress Codex article on WordPress Networks.

Should I use a subdomain or subdirectory multisite?

Formally, VIP Go supports only subdirectory multisites, but this still allows for all the scenarios you need to cover:

  • A pure subdirectory multisite, with sites addresses like example.com/one and example.com/two
  • A site using custom domains, with site addresses like example-another.com and example-further.com
  • A site using a mix of both, with site addresses like example.com, example.com/oneexample-another.com, and example-another.com/two

To achieve the same effect as a subdomain multisite, you could map a number of custom (sub)domains, e.g. one.example.com, two.example.com, etc.

We allow up to two segments in a subdirectory multisite path, so example.com/one (a “one segment” path) and example.com/one/two (a “two segment” path) are supported, but example.com/one/two/three (a “three segment” path) will not work, nor will adding further segments. Configuring a WordPress multisite to allow paths with two segments requires a small amount of code, see below for more details.

Configuring a custom domain, including SSL certificate

Configuring a custom domain requires a few steps, and the site using the custom domain will not be accessible at that domain until all have been completed:

Step 1: Configure the DNS to point to your site. See the documentation on Managing Domains and DNS for more details.

Step 2: From the VIP Dashboard, map the domain.

Step 3: Install an SSL Certificate.  There are two options:

  • A Let’s Encrypt SSL can be activated directly from the VIP Dashboard as soon as the DNS is pointed to our infrastructure and mapped to your application (see steps 1 & 2 above).  This is the most preferable option, as all updates are automated and your team doesn’t need to worry about renewing the certificate.
  • A custom SSL certificate can be procured, please read the SSL documentation for further information.

Step 4: Add the site in the multisite using a temporary path, then edit the site to change the path to / (or up to a two segment path), and to change the domain to your desired custom domain.

Step 5: If you’re using more than one domain per site, set up your vip-config.php file to handle redirecting secondary domains to the desired primary domain for each subsite. Note that for multisite, redirects between non-www domains and www variants need to be specified in vip-config.php. More on multisite domain mapping.

Step 6: Open a support ticket with our team to request a Jetpack connection and optionally, VaultPress connection for the new site.

Subdirectory multisites with two segment paths

VIP Go supports a subdirectory site with a single segment path, e.g. example.com/one, with no additional effort.

With a small amount of additional code, VIP Go can support two path segments, e.g. example.com/one/two (in this configuration it will support both one and two segment paths, as well as custom domains).

To enable paths with two segments, add the following code to /vip-config/client-sunrise.php:

function my_filter_site_by_path_segments_count( $num_segments ) {
        $num_segments = 3;
        return $num_segments;
}
add_filter( 'site_by_path_segments_count', 'my_filter_site_by_path_segments_count', 99 );

If you do not need paths with two segments, there is no need to add the above code.

Which subsite should I launch first?

Subsite 1 is the first site in a multisite network and will be listed first in the Network Admin > Sites listing. VIP strongly recommends launching subsite 1 first. If a client wishes to launch a different subsite first, the first site will need to be set to a non-convenience URL (something other than *.go-vip.net) at launch. While it’s not necessary to keep subsite 1 empty, many clients choose to do so.

Data sync considerations

Note that before performing a data sync between multisite environments, a domain mapping file must be created. Further details about data sync on VIP Go.

Manually logging errors in New Relic

For debugging purposes, you may want to manually log custom errors in PHP code using the trigger_error() function. However, on VIP Go, you have the ability to log custom errors in New Relic using its methods.

The function newrelic_notice_error() is available to log custom errors in New Relic. To trigger an error, you can implement something like the following:

if ( extension_loaded( 'newrelic' ) ) {
	newrelic_notice_error( 'My custom error message' );
}

The function documentation is available here.

Note: if there are multiple calls to newrelic_notice_error() in a single transaction, the PHP agent will retain the exception from the last call only.

Tools for VIP development

VIP offers several tools on the platform to assist development and monitoring. Additionally, the VIP team monitors all sites on the platform, and will proactively notify clients about issues.

The VIP dashboard

The VIP dashboard gives clients a window into their sites hosted on VIP. Here, recent repo activity can be viewed, and database syncs can be performed from production environments to child environments. Please note that the database can only be synced from production to child environments, and not the other way around. For this reason, we’d recommend always authoring content in the production environment.

VIP CLI

VIP CLI is the command line interface for VIP. It can be used to interact with VIP applications, query information about applications, and perform actions like syncing data from production to development environments.

Query monitor

Query Monitor (QM) is available by default on all production and development sites, and can be enabled for any role via code.

New Relic

New Relic is used to monitor the PHP (WordPress) code and browser performance of sites or applications. Access to New Relic is available at no charge for VIP clients.

Further reading

How to use PHP_CodeSniffer during VIP development

PHP_CodeSniffer (aka PHPCS) is a tool that will help you write VIP approved code by ensuring it meets VIP coding standards. Running this tool in your development environment or code editor allows you to fix the errors as you code, helping you develop to VIP best practices, and saving time during review.

PHPCS will provide messages with more information about any errors and warnings found in the codebase scanned.

When running PHPCS, ensure that the WordPress-VIP-Go ruleset is used. This is the ruleset used by VIP in all code review, including via the code analysis bot running in GitHub repositories.

Further reading

Local development for VIP sites

VIP sites run three codebases: WordPress core (tracking the most current version), the VIP Go mu-plugins, and the codebase from your specific site repo. Because of this, a variety of WordPress local development environments can be suitably configured for VIP Go development purposes.

The local development environment can be configured to replicate a VIP environment, including cloning of the GitHub repository. Please see our documentation (linked below) for step-by-step instructions.

Further reading

Code review

VIP’s priority is to ensure that your site is there when you need it, which means we care about its performance and security. Code review is a key component of ensuring your site is secure and performance. We offer both automated checks and manual reviews to clients.

VIP’s code review focuses on the performance and security considerations in PHP, custom JavaScript, and SVG files. We do not review HTML, CSS, SASS, many popular third-party JavaScript libraries, or built JavaScript files.

We’ll schedule an initial code review of the entire code base. You will continue receiving this feedback automatically on all pull requests to your GitHub repository.

We offer two levels of manual code review:

  • Full: A developer will read every line of your code, including themes and custom plugins.
  • Enhanced: Your theme and custom plugins are reviewed line-by-line. Third-party plugins will go through an automated scan (see below).

Automated checks: Even if you don’t receive manual review, your entire code base will be automatically scanned with VIP’s PHP CodeSniffer (PHPCS) standard with an initial report sent to your developers. VIP will answer any questions about specific errors or warnings if the client wishes to refactor the code.

Initial review

For clients on the Full or Enhanced levels of review, please allow for 10-15 business days in your project timeline to complete the first and subsequent review cycles. Please note, exact timeframe can vary depending on various factors – ask your TAE for more details. Before you submit your code for review, ensure it’s been thoroughly tested, scanned using PHPCS with the WordPress-VIP-Go ruleset, and as many errors and warnings as possible have been addressed.

Ongoing review

After the initial review, clients on the Full and Enhanced review levels will have a GitHub pull request workflow enabled. This protects the master branch from merges without our review. The pull request queue is intended to streamline faster deployments, therefore, PRs consisting of more than 1000 lines of reviewable code (PHP, JS, and SVG) will need to be scheduled, and follow the same process and timelines as the initial review.

For clients without manual review, we recommend following a similar workflow to enable the VIP code analysis bot to provide automated feedback.

We take code review seriously and understand that there may be many questions along the way. If you need assistance, please open a ticket and we’d be happy to provide guidance.

Further reading

The VIP platform

The VIP platform (also referred to in our documentation as VIP Go) has container-based infrastructure that allows clients to run core WordPress with custom themes and plugins on Automattic’s world-class hardware and network infrastructure.

On our platform, the codebase consists of core WordPress, a handful of platform-specific mu-plugins, and the client’s custom code. Media is served from the VIP Files Service, a globally distributed object store, and sites are cached on the edge with Varnish. VIP looks after hourly platform backups and 24/7 monitoring.

While your site is in development, it will use a convenience URL ending in .go-vip.net. The production environment tracks the master branch of the connected GitHub repository, which auto-deploys. VIP can also set up child environments for development purposes, which will track a specific branch in the same repo. We also encourage developing locally.

The VIP platform supports both single-site and multisite installations of WordPress.

Tools

VIP clients are offered a variety of tools:

  • Jetpack, VaultPress, and Akismet are connected by default to each VIP Go application.
  • Sites are connected to New Relic for application monitoring.
  • VIP clients can take advantage of supercharged search via Jetpack Search, powered by Elasticsearch.
  • Scanning for coding issues on each PR.

Further Reading

Accessing VIP support

Your support team is here to help, every step of the way.

Zendesk

VIP uses the Zendesk ticketing system for helping support clients with technical issues and launch planning.

Support tickets can be opened in one of these ways:

  • Log in to the Zendesk portal (access provided after kickoff call)
  • Submit tickets from “VIP” portal in the wp-admin of your site
  • Use the ? Support button in the lower left-hand corner of the VIP Dashboard
  • Email support@vip.wordpress.com

Tips for using Zendesk:

  • Keep each issue in its own ticket. This allows our team to effectively route your questions and can help avoid confusion where multiple issues and resolutions are being discussed in one ticket.
  • When you create a ticket, you’ll have the option of four different priority levels: low, normal, high, and urgent. Urgent tickets page the entire support team, so we appreciate that you use them sparingly for true emergencies like outages, time-sensitive security concerns, and workflow-blocking situations where the site isn’t functioning at all.
  • Access your existing tickets in the Zendesk portal.
  • Add additional stakeholders using cc field in the original email, or any subsequent responses.
  • Please follow our guidelines for opening a ticket, to help us reach a timely resolution.

The VIP Lobby

  • The VIP Lobby is the first place we share information with VIP clients from outages to event announcements.
  • To ensure you receive the latest updates, you’ll want to subscribe to new posts. When logged in, you can do this by clicking “Subscribe to Email Updates” or the “Follow” button in the bottom right-hand corner.

 

Follow posts from the VIP Lobby
Follow posts from the VIP Lobby

Twitter

Updates about VIP service issues and maintenance are posted on @WPVIPStatus on Twitter.

Further reading

Validating, sanitizing, and escaping

Your code works, but is it safe? When writing your theme and plugin code, you’ll need to be extra cautious of how you handle data coming into WordPress and how it’s presented to the end user. This commonly comes up when building a settings page for your theme, creating and manipulating shortcodes, or saving and rendering extra data associated with a post. There is a distinction between how input and output are managed, and this document will walk you through that.

(If you’re interested in more thoughts on why WordPress.com VIP takes these practices so seriously, read The Importance of Escaping All The Things from June 2014.)

Guiding Principles

  1. Never trust user input.
  2. Escape as late as possible.
  3. Escape everything from untrusted sources (like databases and users), third-parties (like Twitter), etc.
  4. Never assume anything.
  5. Never trust user input.
  6. Sanitation is okay, but validation/rejection is better.
  7. Never trust user input.

“Escaping isn’t only about protecting from bad guys. It’s just making our software durable. Against random bad input, against malicious input, or against bad weather.”
–nb

Validating: Checking User Input

To validate is to ensure the data you’ve requested of the user matches what they’ve submitted. There are several core methods you can use for input validation; usage obviously depends on the type of fields you’d like to validate. Let’s take a look at an example.

Say we have an input area in our form like this:

<input id="my-zipcode" type="text" maxlength="5" name="my-zipcode" />

Just like that, we’ve limited my user to five characters of input, but there’s no limitation on what they can input. They could enter “11221” or “eval(“. If we’re saving to the database, there’s no way we want to give the user unrestricted write access.

This is where validation plays a role. When processing the form, we’ll write code to check each field for its proper data type. If it’s not of the proper data type, we’ll discard it. For instance, to check “my-zipcode” field, we might do something like this:

$safe_zipcode = intval( $_POST['my-zipcode'] );
if ( ! $safe_zipcode )
$safe_zipcode = '';
update_post_meta( $post->ID, 'my_zipcode', $safe_zipcode );

The intval() function casts user input as an integer, and defaults to zero if the input was a non-numeric value. We then check to see if the value ended up as zero. If it did, we’ll save an empty value to the database. Otherwise, we’ll save the properly validated zipcode.

Note that we could go even further and make sure the the zip code is actually a valid one based on ranges and lengths we expect (e.g. 111111111 is not a valid zip code but would be saved fine with the function above).

This style of validation most closely follows WordPress’ whitelist philosophy: only allow the user to input what you’re expecting. Luckily, there’s a number of handy helper functions you can use for most data types.

Sanitizing: Cleaning User Input

Sanitization is a bit more liberal of an approach to accepting user data. We can fall back to using these methods when there’s a range of acceptable input.

For instance, if we had a form field like this:

<input id="title" type="text" name="title" />

We could sanitize the data with the sanitize_text_field() function:

$title = sanitize_text_field( $_POST['title'] );
update_post_meta( $post->ID, 'title', $title );

Behind the scenes, the function does the following:

  • Checks for invalid UTF-8
  • Converts single < characters to entity
  • Strips all tags
  • Remove line breaks, tabs and extra white space
  • Strip octets

The sanitize_*() class of helper functions are super nice for us, as they ensure we’re ending up with safe data and require minimal effort on our part.

In some instances, using wp_kses and it’s related functions might be a good idea as you can easily clean HTML while keeping anything relevant to your needs present.

Escaping: Securing Output

For security on the other end of the spectrum, we have escaping. To escape is to take the data you may already have and help secure it prior to rendering it for the end user. WordPress thankfully has a few helper functions we can use for most of what we’ll commonly need to do:

esc_html() we should use anytime our HTML element encloses a section of data we’re outputting.


<h4><?php echo esc_html( $title ); ?></h4>

esc_url() should be used on all URLs, including those in the ‘src’ and ‘href’ attributes of an HTML element.

<img alt="" src="<?php echo esc_url( $great_user_picture_url ); ?>" />

esc_js() is intended for inline Javascript.

<div onclick='<?php echo esc_js( $value ); ?>' />

esc_attr() can be used on everything else that’s printed into an HTML element’s attribute.

<ul class="<?php echo esc_attr( $stored_class ); ?>">

wp_kses() can be used on everything that is expected to contain HTML.  There are several variants of the main function, each featuring a different list of built-in defaults.  A popular example is wp_kses_post(), which allows all markup normally permitted in posts. You can of course roll your own filter by using wp_kses() directly.

<?php echo wp_kses_post( $partial_html ); echo wp_kses( $another_partial_html , array( 'a' => array(
        'href' => array(),
        'title' => array()
    ),
    'br' => array(),
    'em' => array(),
    'strong' => array(),
);) ?>

As an example, passing an array to wp_kses() containing the member

'a' => array( 'href' , 'title', )

means that only those 2 HTML attributes will be allowed for a tags — all the other ones will be stripped. Referencing a blank array from any given key means that no attributes are allowed for that element and they should all be stripped.

There has historically been a perception that wp_kses() is slow. While it is a bit slower than the other escaping functions, the difference is minimal and does not have as much of an impact as most slow queries or uncached functions would.

It’s important to note that most WordPress functions properly prepare the data for output, and you don’t need to escape again.

<h4><?php the_title(); ?></h4>

rawurlencode() should be used over urlencode() for ensure URLs are correctly encoded. Only legacy systems should use urlencode()`.

<?php echo esc_url( 'http://example.com/a/safe/url?parameter=' . rawurlencode( $stored_class ) ); ?>

Always Escape Late

It’s best to do the output escaping as late as possible, ideally as data is being outputted.

// Okay, but not that great
$url = esc_url( $url );
$text = esc_html( $text );
echo '<a href="'. $url . '">' . $text . '</a>';

// Much better!
echo '<a href="'. esc_url( $url ) . '">' . esc_html( $text ) . '</a>';

This is for a few reasons:

  • It makes our code reviews and deploys happen faster because rather than hunting through many lines of code, we can glance at it and know it’s safe for output.
  • Something could inadvertently change the variable between when it was firstly cast and when it’s outputted, introducing a potential vulnerability.
  • Future changes could refactor the code significantly. We review code under the assumption that everything is being output escaped/cast – if it’s not and some changes go through that make it no longer safe to output, we may incorrectly allow the code through, since we’re assuming it’s being properly handled on output.
  • Late escaping makes it easier for us to do automatic code scanning (saving us time and cutting down on review/deploy times) – something we’ll be doing more of in the future.
  • Escaping/casting on output simply removes any ambiguity and adds clarity (always develop for the maintainer).

Escape on String Creation

It is sometimes not practical to escape late. In a few rare circumstances you cannot pass the output to wp_kses since by definition it would strip the scripts that are being generated.

In situations like this always escape while creating the string and store the value in a variable that is a postfixed with _escaped, _safe or _clean. So instead of $variable do $variable_escaped or $variable_safe.

If a function cannot output internally and late escape, then it must always return “safe” html, that does not rely on them being late escaped. This allows you to do echo my_custom_script_code(); without needing the script tag to be passed through a version of wp_kses that would allow such tags.

Case Studies and FAQs

We know that validating, sanitizing and escaping can be a complex topic; we’ll add some specific case studies and frequently asked questions here as we think they might be helpful.

Q: Doesn’t a function like WP_Query handle sanitizing user input before running a query for me? Why do I need to also sanitize what I send to it?

A: For maximum security, we don’t want to rely on WP_Query to sanitize our data and hope that there are no bugs or unexpected interactions there now or in the future. It’s a good practice to sanitize anything coming from user-land as soon as you begin to interact with it, treating it as potentially malicious right away.

Q: Isn’t WP_KSES_* slow?
A: Even on large strings WP_KSES_* will not add a significant overhead to your pageload. Most of your pageloads should be cached pageloads and the first thing to focus on should be to make sure as many of your end users as possible are getting cached pages. Slow SQL Queries as well as Remote requests are often next on the list. Escaping is often negligible compared to those items.

Zack Tollman wanted to know more about wp_kses functions, so he did a pretty thorough investigation about them here. https://www.tollmanz.com/wp-kses-performance/. He found that wp_kses functions can be 20-40x slower than esc_* functions on PHP 5.6, but the performance hit is much smaller when using HHVM. The post was written before PHP 7 came out, but PHP 7 is likely to have similar performance to HHVM, meaning that wp_kses functions aren’t as much as a performance drain as they used to be, at least on PHP 7 servers. WordPress.com is using PHP 7.

Q: Why do I need to escape these values? It is impossible for them to be unsafe.
A: It is currently impossible for them to be unsafe. But a later code change could easily make it that the variable is modified and therefore can no longer be trusted. Always late escaping whenever possible makes the code much more robust and future proof.

Conclusion

To recap: Follow the whitelist philosophy with data validation, and only allow the user to input data of your expected type. If it’s not the proper type, discard it. When you have a range of data that can be entered, make sure you sanitize it. Escape data as much and as late as possible on output to avoid XSS and malformed HTML.

Take a look through the Data Validation Plugin Handbook page  to see all of the sanitization and escaping functions WordPress has to offer.

Fetching remote data

If you need to fetch data from another server, you should remember that doing so is a relatively slow process and that you can run into problems if there are any timeouts.

To help you to efficiently and robustly fetch your data, we have created two helper functions that you can use:

wpcom_vip_file_get_contents()

wpcom_vip_file_get_contents() works much like PHP’s built-in file_get_contents() function (although it no longer internally uses it). It returns either the HTML result as a string or false on failure. However, it caches and even returns previously cached data if a new remote request fails. We strongly recommend using this function for any remote request that does not require receiving fresh, up-to-the-second results, i.e. anything on the front end of your blog.

  1. The URL you want to fetch. This is the only required argument.
  2. The timeout limit in seconds. Can be 1 to 10 seconds and it defaults to 3 seconds. We strongly discourage using a timeout greater than 3 seconds since remote requests require that the user wait for them to complete before the rest of the page will load.
  3. The minimum cache time in seconds. It cannot be less than 60 and it defaults to 900 (15 minutes). Setting this higher will result in a faster site as remote requests are relatively slow. Results may be cached even longer if the remote server sends a cache-control header along with its response, and if that value is larger than this value. See below for details and how to disable this.
  4. An array of additional advanced arguments. See below.

The fourth parameter is an optional argument that can be used to set advanced configuration options. The current additional advanced arguments are:

  • obey_cache_control_header — By default, if the remote server sends a cache-control header with a max-age value that is larger than the cache time passed as the third parameter of this function, then this remotely provided value will be used instead. This is because it’s assumed that it’s safe to cache data for a longer period of time if the remote server says the data is not going to change. If you wish to ignore the remote server’s header response and forcibly cache for only the time specified by the third parameter, then a function call along these lines should be used:
    echo wpcom_vip_file_get_contents( 'http://example.com/file.txt', 3, 900,
    array( 'obey_cache_control_header' => false ) );
    
  • http_api_args — Allows you to pass arguments directly to the wp_remote_get() call. See the WordPress.org Code Reference for a list of available arguments. Using this argument will allow you to send things like custom headers or cookies. Example usage:
    echo wpcom_vip_file_get_contents( 'http://example.com/file.txt', 3, 900,
    array( 'http_api_args' => array( 'headers' => array( 'Accept-Encoding' => 'gzip' ) ) ) );
    

Note that like PHP’s file_get_contents() function, wpcom_vip_file_get_contents() will return the result. You will need to echo it if you want it outputted. This is different from our previous and now deprecated functions, including vip_wp_file_get_contents().

vip_safe_wp_remote_get()

vip_safe_wp_remote_get() is a sophisticated extended version of wp_remote_get(). It is designed to more gracefully handle failure than wp_safe_remote_get() does. Note that like wp_remote_get() and wp_safe_remote_get, it does not cache. Its arguments are as follows:

  1. The URL you want to fetch. This is the only required argument.
  2. This argument is optional. Pass false if you need to set any of the next arguments.
  3. The number of fails required before subsequent requests automatically return the fallback value. This prevents continually making requests and receiving timeouts for a down or slow remote site. Defaults to 3 retries. Cannot be more than 10.
  4. The number of seconds before the request times out. Can be 1, 2, or 3 and it defaults to 1 second.
  5. This argument controls both the number of seconds before resetting the fail counter and the number of seconds to delay making new requests after the fail threshold is reached. Defaults to 20 and cannot be less than 10.

If you’re confused, here’s some examples that should help clarify:

// Get a URL with a 1 second timeout and cancel remote calls for
// 20 seconds after 3 failed attempts in 20 seconds have occurred
$response = vip_safe_wp_remote_get( $url );
if ( is_wp_error( $response ) )
	echo 'No data is available.';
else
	echo wp_remote_retrieve_body( $response );

// Get a URL with 1 second timeout and cancel remote calls for 60 seconds
// after 1 failed attempt in 60 seconds has occurred. On failure, display &quot;N/A&quot;.
$response = vip_safe_wp_remote_get( $url, false, 1, 1, 60 );
if ( is_wp_error( $response ) )
	echo 'N/A';
else
	echo wp_remote_retrieve_body( $response );

fetch_feed()

WordPress’s built-in fetch_feed() function should be used for fetching and parsing feeds. It has built-in caching that defaults to 43200 seconds (12 hours). To change that value, use a filter:

function someprefix_return_900() {
	return 900;
}

add_filter( 'wp_feed_cache_transient_lifetime', 'someprefix_return_900' );
$feed = fetch_feed( $feed_url );
remove_filter( 'wp_feed_cache_transient_lifetime', 'someprefix_return_900' );

wpcom_vip_wp_oembed_get()

`wpcom_vip_wp_oembed_get()` is a wrapper for WordPress’ own `wp_oembed_get()` but with added caching.

Uncached Remote Requests

If for some reason you need to make an uncached remote request, such as to ping an external service during post publish, then you should use the powerful and flexible WordPress HTTP API rather than directly using cURL or another method.

Note that uncached remote requests should never run on the front end of your site for speed and performance reasons.

cURL fopen fsockopen

Use current_time(), not date_default_timezone_set()

Use WordPress’s

current_time( 'timestamp' )

if you need to get a time that’s adjusted for the site’s timezone setting in the admin area.

If you need to work with the timezone offset:

get_option( 'gmt_offset' )

Please don’t use date_default_timezone_set(). The timezone in PHP needs to stay GMT+0 as that’s what WordPress expects it to be. Several features are dependent on this, and will break if you adjust the timezone.

Custom user roles

Sometimes the default roles and capabilities aren’t exactly what you need for your site. If you need to create new roles or modify existing ones, we have helper functions to assist you in doing this. Please use these functions rather than the traditional methods as this will ensure that your code works on WordPress.com and in your development environments.

As an example, here’s how you can register a “Reviewer” role:

add_action( 'init', function() {
    $ver = 42; // bump each time this code is changed
    // check if this has been run already
    if ( $ver <= get_option( 'custom_roles_version' ) {
        return;
    }

    // add a Reviewer role
    wpcom_vip_add_role( 'reviewer', 'Reviewer', array(
        'read' => true,
        'edit_posts' => true,
        'edit_others_posts' => true,
        'edit_private_posts' => true,
        'edit_published_posts' => true,
        'read_private_posts' => true,
        'edit_pages' => true,
        'edit_others_pages' => true,
        'edit_private_pages' => true,
        'edit_published_pages' => true,
        'read_private_pages' => true,
        )
    );

    // update the version to prevent this running again
    update_option( 'custom_roles_version', $ver );
} );

Note: you’ll want to use these helper functions on the ‘init’ hook, and ensure you only run them when the role definitions need to change. An example technique is shown.

You can find all available capabilities in WordPress Codex.

Here are some more examples:

add_action( 'init', function() {
    $ver = 43; // bump each time this code is changed
    // check if this has been run already
    if ( $ver <= get_option( 'custom_roles_version' ) {
        return;
    }
    
    // Add new role
    wpcom_vip_add_role( 'super-editor', 'Super Editor', array( 'level_0' => true ) );

    // Remove publish_posts cap from authors
    wpcom_vip_merge_role_caps( 'author', array( 'publish_posts' => false ) );

    // Remove all caps from contributors
    wpcom_vip_override_role_caps( 'contributor', array( 'level_0' => false ) );

    // Duplicate an existing role and modify some caps
    wpcom_vip_duplicate_role( 'administrator', 'station-administrator', 'Station Administrator',
        array( 'manage_categories' => false ) );

    // Add custom cap to a role
    wpcom_vip_add_role_caps( 'administrator', array( 'my-custom-cap' ) );

    // Remove cap from a role
    wpcom_vip_remove_role_caps( 'author', array( 'publish_posts' ) );

    // update the version to prevent this running again
    update_option( 'custom_roles_version', $ver );
} );

Powered by WordPress.com VIP

VIP always appreciates our clients displaying a “Powered by WordPress.com VIP” link in the footer of their theme. Service agreements with VIP may include this as a requirement, so please get in touch with any specific questions.

Most people put this link in the footer of their website. Rather than hardcoding this link, we would prefer if you used the existing helper function to do it, vip_powered_wpcom().

Example:

<?php echo vip_powered_wpcom(); ?>

If called with no arguments, vip_powered_wpcom() will return the following:

Powered by <a href="http://wpvip.com/" rel="generator">WordPress.com VIP</a>

However you can customize that to better fit your site if you wish.

The first argument controls the output type. The default is text, but you can also pass the numbers 1 through 6 to use one of these images instead of text:

1.
2.
3.
4.
5.
6.

The second parameter only applies to the text format and controls the text before the link. It defaults to “Powered by ” (note the trailing space).

Database queries

Direct database queries should be avoided wherever possible. Instead, it’s best to rely on WordPress API functions for fetching and manipulating data.

Of course this is not always possible, so if any direct queries need to be run here are some best practices to follow:

  • Use filters to adjust queries to your needs. Filters such as posts_where can help adjust the default queries done by WP_Query. This helps keep your code compatible with other plugins. There are numerous filters available to hook into inside /wp-includes/query.php.
  • Make sure that all your queries are protected against SQL injection by making use of $wpdb->prepare and other escaping functions like esc_sql and like_escape.
  • Try to avoid cross-table queries, especially queries which could contain huge datasets such as negating taxonomy queries like the -cat option to exclude posts of a certain category. These queries can cause a huge load on the database servers.
  • Remember that the database is not a tool box. Although you might be able to perform a lot of work on the database side, your code will scale much better by keeping database queries simple and performing necessary calculations and logic in PHP.
  • Avoid using DISTINCT, GROUP, or other query statements that cause the generation of temporary tables to deliver the results.
  • Be aware of the amount of data you are requesting. Make sure to include defensive limits.
  • When creating your own queries in your development environment, be sure to examine the query for performance issues using the EXPLAIN statement. Confirm indexes are being used.
  • Don’t JOIN the users table.
  • Cache the results of queries where it makes sense.

Uncached functions

WordPress core has a number of functions that, for various reasons, are uncached, which means that calling them will always result in an SQL query. Below, we outline some of these functions:

  • get_posts()
    • Unlike WP_Query, the results of get_posts() are not cached via Advanced Post Cache.
    • Use WP_Query instead, or set 'suppress_filters' => false.
      $args = array(
      	'post_type'        => 'post',
      	'posts_per_page'   => 3,
      	'suppress_filters' => false,
      );
      $query = get_posts( $args );
      
    • When using WP_Query instead of get_posts don’t forget about setting ignore_sticky_posts and no_found_rows params appropriately (both are hardcoded inside a get_posts function with value of true )
  • wp_get_recent_posts()
    • See get_posts()
  • get_children()
    • Similar to get_posts(), but also performs a no-LIMIT query among other bad things by default. Alias of break_my_site_now_please(). Do not use. Instead do a regular WP_Query and make sure that the post_parent you are looking for is not 0 or a falsey value. Also make sure to set a reasonable posts_per_page, get_children will do a -1 query by default, a maximum of 100 should be used (but a smaller value could increase performance)
  • term_exists()
    • Use wpcom_vip_term_exists() instead
  • get_page_by_title()
  • get_page_by_path()
    • Use wpcom_vip_get_page_by_path() instead
  • url_to_postid()
    • Use wpcom_vip_url_to_postid() instead
  • count_user_posts()
    • Use wpcom_vip_count_user_posts() instead.
  • wp_old_slug_redirect()
    • Use wpcom_vip_old_slug_redirect() instead.
  • get_adjacent_post()get_previous_post()get_next_post(), previous_post_link(), next_post_link()
    • Use  wpcom_vip_get_adjacent_post() instead.
  • attachment_url_to_postid()
    • Use  wpcom_vip_attachment_url_to_postid() instead.
  • wp_oembed_get()
    • Use wpcom_vip_wp_oembed_get() instead.

Ready to get started?

Drop us a note.

No matter where you are in the planning process, we’re happy to help, and we’re actual humans here on the other side of the form. 👋 We’re here to discuss your challenges and plans, evaluate your existing resources or a potential partner, or even make some initial recommendations. And, of course, we’re here to help any time you’re in the market for some robust WordPress awesomeness.