When you work with Neos CMS for as many years as I do, you usually know the tricks by heart on how to create a fast Neos CMS website. But I see and hear regularly discussions about that topic and when I join an existing Neos project I often see some easy to fix mistakes regarding performance.
As I recently sped up an existing website by the factor of 10x, I decided to write some hints in this blog post to help others. Maybe even some Neos experts can learn a thing or two.
Even though your site will be as fast as a rocket, it's not rocket science to do this.
What you can expect from this guide
When you follow this recipe you should have a much faster Neos website at the end.
Most hints in this post are valid for small, medium and big websites.
Some optimizations only make a noteworthy difference for bigger websites, but I will point that out for each one.
I’ll not explain every feature and configuration in all details, and neither how to install the necessary software as there are much better tutorials for that on the web and would make this post far too long. But I'll provide some links that should help your getting started.
For most hosting environment you should be able to expect a response time from your website between 60-200ms for cached requests, based on the complexity of your site and your hosting.
Basic ingredients
You should have the following ingredients available for a fast Neos CMS website:
- Webserver (NGINX or Apache)
- PHP 7+
- MySQL or MariaDB
- A running Neos CMS website of course
Optional advanced ingredients:
- Redis
- Varnish
- Cloud CDN provider
- ElasticSearch
The webserver
The webserver is the main entry point into being able to server any content.
You can use NGINX or Apache, but most of the time the difference is not that big.
I personally prefer NGINX as the configuration is easier to read for me and it performs faster when delivering static content like images. You can also enable a proxy cache for your html, but this needs some more advanced configuration. I will get to that topic a bit later in this post.
You can find an example configuration here which can be used with PHP-FPM.
Apache on the other hand also works fine and Neos includes by default a working .htaccess file in it’s Web folder with the necessary configuration to get started.
Performance hint 1: The application context
Neos CMS works with an application context called „FLOW_CONTEXT“. This context is basically an environment variable that tells Neos in which kind of mode it should run.
By default this is set to „Development“. This means Neos thinks you are working on it, you need better error feedback and it scans with every request if you changed files.
This of course makes the whole system slower but is necessary when your are actively developing your website.
When you setup your production website that should server actual visitors, you should definitely set this context variable to „Production“. You can do this for example in the .htaccess for Apache or in the NGINX configuration. There are other ways to do this too, but this is the most common method.
How to change this for Apache:
In the included .htaccess file in the Neos CMS „Web“ folder you can find a line like:
SetEnv FLOW_CONTEXT Production
Make sure this line is not a comment, which means it doesn’t start with a „#“.
How to change this for NGINX:
You should have a line like this in your location block for php when using fastcgi:
fastcgi_param FLOW_CONTEXT Development/Local;
This change should already speed up your website by a factor of 2 or 3 and only takes a minute to do. Especially in the backend of Neos this makes an even bigger difference than in the frontend as the backend sends more requests to the server as a single page view in the frontend.
PHP
As Neos CMS and it’s underlying Flow Framework are written in PHP they heavily rely on the Performance of the used PHP version.
Neos CMS 2.3 LTS supports PHP 5.6 up to 7.1. And the more recent Neos CMS versions 3.3 LTS and 4.3 LTS support PHP up to 7.3.
I usually use PHP in its FPM variant and connect from the webserver via a socket. But this shouldn't matter that much in most cases. So loading PHP as Apache module or another way will yield similar results. PHP-FPM with a socket should have less overhead when the webserver connects to it and manages resources better.
Performance hint 2: The PHP Version
I absolutely recommend using the last PHP version that your installed Neos version supports, as the performance gain of PHP 7.1 compared to 5.6 is about a factor of 1,5 - 2.
When you switch the version on a running site, you should clear the caches of Neos afterwards by running
./flow flow:cache:flush —force
I also tend to add the target PHP version to the composer.json file to prevent composer from installing dependencies that might not work with the environments PHP version and to show errors if there are no compatible versions.
You can do this by adding the „platform“ and „php“ settings to your composer.json in the existing „config“ block:
"config": {
"vendor-dir": "Packages/Libraries",
"bin-dir": "bin",
"platform": {
"php": "7.1.28"
}
}
Performance hint 3: Enable the OPCache module
But this benefit only fully shines when the OPCache module of PHP is enabled.
This module caches the latest compiled PHP files and this means, the server has less work when the same files are required again for rendering a site.
For a CMS this of course has big benefits as mostly the same PHP files are required.
I saw that some managed servers don't have this module enabled by default.
It might also be a good idea to increase the memory the module is allowed to use to 128 or 258MB in the PHP configuration.
The database
Neos CMS supports a range of databases. This includes MySQL and its compatible Open Source fork MariaDB and also PostgreSQL.
I only have experience with MySQL and MariaDB, but from what I know they all mostly behave the same. There should be some theoretical performance gains (~20% for certain queries) with MariaDB in comparison to MySQL, but I never tested both with the same big website to see the actual difference. Usually I tend to use MariaDB as I support the Open Source flavor.
You can find some explanation about the differences here.
I never had to change other default settings in my projects, but this might be necessary for bigger sites or depending on your hosting as the default settings might be different.
The CMS
Of course Neos itself has also some settings that are important for a sites performance.
As Neos uses the previously mentioned „FLOW_CONTEXT“ to already set most things correctly, there are some other settings you might want to adjust.
Caching backend
By default, Neos creates a lot of caches to speed up the delivery of content for subsequent requests. These cached files are stored in the filesystem (usually in Data/Temporary) and include routing information, rendered HTML parts, modified PHP files, localizations and more.
As the amount of files and requests to those files can get more when your site and its code are growing, the server might spend a lot of time reading them.
Performance hint 4: Change the caching backend to Redis
The recommended caching backend is Redis which is an in-memory key/value storage. The service needs to run on your server (or somewhere in your stack) and the Redis module for PHP needs to be enabled.
The caching configuration should be done in a file called „Caches.yaml“ which you should put alongside your „Settings.yaml“. I use Redis also during development, but you can of course only put this file into the „Configuration/Production“ folder (or do it in a way that fits your deployment process).
A „Caches.yaml“ with Redis for Neos 3.0 or newer:
Flow_Mvc_Routing_Route:
backend: Neos\Cache\Backend\RedisBackend
backendOptions:
database: 0
Flow_Mvc_Routing_Resolve:
backend: Neos\Cache\Backend\RedisBackend
backendOptions:
database: 0
Neos_Fusion_Content:
backend: Neos\Cache\Backend\RedisBackend
backendOptions:
database: 0
Neos_Neos_Fusion:
backend: Neos\Cache\Backend\RedisBackend
backendOptions:
database: 0
I usually achieved a performance gain of ~30% with this configuration and it might even be greater when your site is hit with a lot of requests.
For shared & managed hosting, often Redis is not available, so you can instead use some alternatives that are less powerful but also help for small & medium size websites.
When running several sites on the same server, you will need to change the database number for each site, so they don't interfere with each other.
Alternative: Use the PDO Backend
The PDO backend uses a normal database to store the cached information. The simplest way is to use SQLite which your server and PHP needs to support. But other databases like MySQL are also supported but need a bit of configuration. All servers I encountered that were missing Redis usually had SQLite support. Read more about this topic and even more cache backends here.
A basic caching configuration with the PDO backend and SQLite would look like this:
Flow_Mvc_Routing_Route:
backend: Neos\Cache\Backend\PdoBackend
backendOptions:
defaultLifetime: 0
dataSourceName: 'sqlite:/var/www/example.org/shared/sqlite.db'
Flow_Mvc_Routing_Resolve:
backend: Neos\Cache\Backend\PdoBackend
backendOptions:
defaultLifetime: 0
dataSourceName: 'sqlite:/var/www/example.org/shared/sqlite.db'
Neos_Fusion_Content:
backend: Neos\Cache\Backend\PdoBackend
backendOptions:
defaultLifetime: 0
dataSourceName: 'sqlite:/var/www/example.org/shared/sqlite.db'
Neos_Neos_Fusion:
backend: Neos\Cache\Backend\PdoBackend
backendOptions:
defaultLifetime: 0
dataSourceName: 'sqlite:/var/www/example.org/shared/sqlite.db'
This configuration should work fine with small sites and medium sites too. But I would advise to switch to Redis when your sites get bigger, as it’s optimized for doing this task.
The content cache configuration
Neos CMS allows you a very fine grained control over what and how your content parts are cached. You define these configurations in Fusion alongside your rendering definitions of your node types. Neos CMS will by default only cache full pages, but you can also optimize this further by caching smaller parts, which will be reused when other pages are being rendered.
Performance hint 5: Cache commonly used Fusion objects
There are parts of your website that are mostly the same for all subpages. Most of the time this is the site header (with some caveats), footer and included scripts & styles.
By defining a caching configuration (read more about this here) Neos doesn’t need to rebuild those parts again and again for every subpage. So if you have a complex menu in your header and footer, those computations are only done once in the best case. This of course can be different the more complex your site gets, but it’s usually worth the effort.
For example a footer might get the following cache configuration in Fusion:
@cache {
mode = 'cached'
entryIdentifier {
identifier = 'footer'
site = ${site}
}
entryTags {
site = ${Neos.Caching.nodeTag(site)}
footerContent = ${Neos.Caching.descendantOfTag(q(site).children('footer'))}
metamenu = ${Neos.Caching.nodeTag(q(site).property('metamenu'))}
metamenuChildren = ${Neos.Caching.descendantOfTag(q(site).property('metamenu'))}
}
}
The important part here is the „entryIdentifier“, as it’s a static string and therefore Neos will only build this object once and then reuses the cached version.
The „entryTags“ are used for clearing the cached footer when it’s content or related menu entries are being cached. These tags might look completely different in your setup and you might even need none of the ones in the example.
This is applicable for other Fusion objects too. I like to do this especially for the site header as it contains the most complex menus. But caching menus will make you loose their item states like „current“ and „active“. This can be solved based on your requirements by either splitting the menu into several objects with their individual caching configuration, or by highlighting the currently selected menu items via javascript.
You can also add caching configurations to the Fusion code of content that will appear in several places. For example teasers or author widgets that might be shown below your main content on many pages and in search results.
These optimizations might not improve the performance of your homepage, but other subpages will be generated much faster, when they are not already cached.
Note: when you use Neos.Caching.nodeTypeTag in your entryTags, make sure to provide the current node as second parameter. This will prevent flushing of caches in non related workspaces.
Note: there is a "secret" entryIdentifier that the content cache will add to your own entry identifiers before storing the cached segment. This secret is the fusionPath of the currently rendered element. So for our example the fusionPath would be the same if you render the footer in the same place for the same page type. But for different page types this fusionPath would be different and therefore lead to a separate cache entry. I will show how to solve this in another blog post.
Proxy caching (advanced)
By now your site is much faster, but you might want to have better performance when there is a lot of traffic or when your site is accessed from all over the globe.
Performance hint 6: Use the Varnish http cache
To reduce the server load and have consistent & fast response times you can use the Varnish HTTP Cache service (https://varnish-cache.org).
Most hosting environments will not have Varnish readily available, so you should check with your hoster if you can use it or install it yourself if possible.
When you have Varnish available on your server, the setup is quite easy. Basically you install the Varnish package and follow its documentation on how to set it up. In most cases this is all the setup you need.
After you do this Varnish will cache and deliver all your rendered sites and for most requests, Neos will not have to do anything anymore. For dynamic content like forms and search result pages, you should have a cache configuration in Fusion that will tell the system that the content is not always the same. Then Varnish will follow those rules too.
Alternative: NGINX proxy caching
If you cannot use Varnish or you don't want to use it, there is also the alternative of using NGINX as your proxy cache service. This cache has less features than Varnish, but can actually perform faster and you don't need a complex configuration for it, as the configuration is just added to your existing virtual host (when you already use NGINX).
The configuration is short and I implemented a package for Neos that allows a better integration. Be sure to check its current limitations.
Performance hint 7: Use a content delivery network
By now your site is super fast, but only in the area where your server is hosted. When your server is in Europe, your visitors from Australia might not be happy with the added delay. You can solve this in several steps by using a CDN like Cloudflare or KeyCDN.
When you setup the basic and free Cloudflare account on your site your will already get the benefit that your static assets like images will be optimized, cached and delivered by servers which are as close as possible to your visitors origin. This already helps a lot with image heavy sites, which are quite common.
Cloudflare will not by default cache and deliver your HTML as it doesn’t know if you have changed your content.
With paid CDN accounts you have a lot more possibilities for optimizations and you can even cache your HTML. But for this to work well, you will need a Neos package that will tell the CDN what and when to cache.
You can have a look at the experimental package for Cloudflare.
Performance hint 8: Use the Flowpack FullPageCache
Neos itself still needs the database to determine which cache entries should be sent to the visitor. Even when caching is fully warmed and configured in the best way there are still a few queries to run.
When using a proxy cache, Neos is not even started but we need an additional service and configuration to make it work well.
But with the release of Neos 5.1 a new plugin from the Neos core team was released that also supports Neos 4.3 and provides a full page cache that doesn't necessarily need the database.
The plugin Flowpack.FullPageCache caches the fully rendered pages based on Neos own caching configuration. So it knows also exactly when caches are expired or pages are not cacheable. The great thing: when a cache entry is there no data from the content repository needs to be fetched. Therefore often no database query at all has to be run.
This reduces response time immensely. In my projects to about 25-40ms from 60-180ms. So far I had only minor problems which have already been fixed in the plugin.
The feature was not fully integrated into the Neos core yet as we needs more real world testing to get everything right. There are some projects which have some special setup which cause issues with this cache. Therefore its safer for now to have it as an optional plugin.
So try it and maybe you don't need a proxy cache anymore!
Faster node queries
If your site grows your queries in Fusion might get slow as PHP needs to filter and search through 100.000s of nodes.
Performance hint 8: Use Elasticsearch for node queries
Neos CMS has great support for Elasticsearch which is basically a search engine. Most often you would use this to build a search feature for your website, but you can also use it to speed up your queries.
Like with Varnish you need to have the Elasticsearch service running on your server or in your stack and have the package for Neos installed.
Afterwards you can use additional Fusion functions to query for nodes with Elasticsearch instead of using Neos internal slower PHP methods.
One use case can be listing all blog posts with certain categories on an overview page. When you have hundreds of posts, this can be much faster.
I even used Elasticsearch in some more use cases where I encountered performance bottlenecks like when checking whether a user is allowed to see certain content or for speeding up certain backend operations or data sources.
Example: If you do something like the following in Fusion to render a footer navigation:
footerMenuItems = ${q(site).find('[instanceof Neos.Neos:Document]').filter('[showInFooterMenu = true]')}
This will get very slow when your site is growing as every page needs to be checked on a property. As node properties are not indexed in the database this query takes a long time.
So instead do the same with Elasticesearch to get the list of footer items much faster:
footerMenuItems = ${Search.query(site).nodeType('Neos.Neos:Document').exactMatch('showInFooterMenu', true).limit(20).execute()}
Final words
I hope these performance hints helped you building a faster Neos CMS website.
Your visitors & editors will have a better experience!
Neos CMS loves to be fast and just needs a cozy environment to show its power!
If you have questions or notes about these topics, feel free to contact me or you can even hire me if you need help improving your sites performance.
Updated 11.6.2019: Added notes about NGINX proxy caching
Updated 17.6.2019: Updated PDOBackend example to disable cache timeout after 1 hour
Updated 17.10.2019: Adjusted cached footer to respect the site nodes context
Updated 12.11.2019: Added example for footer navigation with Elasticsearch
Updated 07.02.2020: Added information about Flowpack.FullPageCache