Skip to content
Shops & CMS

Slow Shop Backend: Speeding Up the Admin for Your Team

When the Shopware admin turns sluggish in peak season, four levers help: CLI workers instead of browser tabs, admin search, lean lists and nightly indexing.

17 min read ShopwareAdministrationServer

In short and in plain language

This article is about the admin of a Shopware shop. There your team handles products and orders. If it is slow, it costs working time.

By default, background tasks run in the browser. When many people work at the same time, it gets slow. It is better to run these tasks on the server.

A separate search helps with large product lists. Lists with fewer columns load faster.

Large upkeep jobs belong in the night. Measure during working hours.

This matters most before the Christmas season. We help you with this.

Your customers only see the storefront of your shop. Your team sees the admin: processing orders, maintaining products, uploading images, creating vouchers. When that interface turns sluggish, every wait costs working time, and it does so precisely when there is the most to do. In many shops, more people work in the admin at the same time before and during the Christmas season than in the rest of the year (project experience). In Shopware, the speed of the admin mainly depends on four things: how background tasks are processed, the admin search, heavy lists, and indexing and maintenance runs that start in the middle of the working day. This article shows how to measure each of them and switch it over, using the settings that the Shopware documentation itself names for production.

Key takeaways

  • Out of the box, Shopware processes background tasks in the browser as long as someone is logged into the admin. With several users logged in, this causes high CPU load and interferes with PHP-FPM (Shopware documentation).
  • In production, the queue belongs in CLI workers kept running by systemd or Supervisor; according to Shopware, up to three worker processes (Shopware documentation) are normal.
  • For large data sets, OpenSearch takes load off the admin search. Since Shopware 6.7.9.0 (Shopware documentation), it can also serve admin lists and filters as an experimental feature.
  • Lists with page numbers need an exact total count, and the documentation describes exactly this count as performance-intensive. Fewer columns and lean queries in extensions keep lists fast.
  • Reindexing, sitemap generation and image runs belong at night: since Shopware 6.7.1.0 (Shopware documentation), the scheduled sitemap task can be switched off and started via cron.
  • Measure during working hours with real roles: network tab, FPM status page and the slow query log, whose default threshold is 10 seconds (MySQL Reference Manual).

Why the admin slows down when more people work in it

The Shopware admin is an application that runs in the browser and fetches every list, every search and every save from the server via the Admin API. These requests usually end up in the same PHP process pool as your customers’ page views. There, the pm.max_children setting limits how many requests are served at the same time (PHP manual). Once all processes are busy, the next request waits, regardless of whether it comes from a customer in checkout or from a colleague in the orders module.

The same perception limits apply to the admin as anywhere else: up to about 0.1 seconds (Nielsen Norman Group 1993), a reaction feels instantaneous. Up to about 1 second (Nielsen Norman Group 1993), the flow of thought stays uninterrupted, even though the delay is noticeable. From about 10 seconds (Nielsen Norman Group 1993), attention drifts and people start doing something else on the side. For the response to a click, an INP of 200 milliseconds (web.dev) or less counts as good. An order list that needs several seconds after every filter change is far beyond what feels smooth.

In everyday work, this rarely stands out as a single event. It shows up as a sum: the list takes a little longer to load, saving drags on, the search reacts late to the input. Anyone who touches many orders a day loses these seconds over and over again. And because the admin uses the same server as the shop, an overloaded admin can also worsen the response time of the storefront.

Scope

This article is about the working speed of staff in the shop admin. How to measure speed in logged-in areas for your customers, such as customer portals, is covered in Speed behind the login.

The admin worker: background work in a browser tab

Shopware does not complete many tasks immediately but puts them into a queue: indexing after a product is saved, thumbnails after an upload, scheduled maintenance tasks. By default, these tasks are stored in the database and processed via the browser as long as someone is logged into the admin (Shopware documentation). The documentation explicitly calls this simple and fast for development but not recommended for production systems, and gives this reason:

With multiple users logged into the Administration, this can lead to a high CPU load and interfere with the smooth execution of PHP FPM.

Shopware documentation, Message Queue

Technically, the admin worker works like this: the open admin sends requests to the /api/_action/message-queue/consume endpoint. Within such a request, the server processes messages from the queue until the poll interval has elapsed; then the request ends and the admin starts the next one (Shopware documentation). By default, this interval is set to 20 seconds (Shopware source code). As long as there is work, such a request therefore keeps a PHP process busy for up to that long, not for what the person is doing, but for tasks that anyone in the shop has triggered. If the queue is empty, the request returns early (Shopware source code).

Several people in the admin mean more of these requests. To keep the browsers from getting in each other’s way, Shopware locks each queue to a single consumer; the other requests receive an error instead of work (Shopware source code). The load does not disappear; it moves back and forth between the tabs, and every request still goes through authentication, permission check and lock, on the same PHP processes that serve the colleagues’ clicks. Then there is the reverse case: when nobody is logged in, the work piles up. Whatever accumulated overnight is processed in the morning by the first browser that opens the admin.

FeatureAdmin worker (default)CLI worker (production)
Where the work runsin requests from open browser tabsin separate processes on the server
When it runsonly while someone is logged inaround the clock, even without a login
What carries itthe PHP-FPM pool that also serves shop and admindedicated processes with limits for runtime and memory
How many in parallelone per queue, further tabs request in vaina fixed number, set and monitored
Recommended by the documentation fordevelopmentproduction systems

Switching to the CLI worker

For production systems, the documentation recommends processing the queue via the command line. Tasks are then completed even when nobody is logged in, and the high CPU load caused by several people in the admin goes away (Shopware documentation). The switch has three parts: run the workers as a service, switch off the admin worker, and trigger scheduled tasks separately.

config/packages/shopware.yaml
# config/packages/shopware.yaml
# Switch off the admin worker once the CLI workers are running
shopware:
    admin_worker:
        enable_admin_worker: false

The workers themselves run as a service that systemd or Supervisor restarts as needed. The documentation provides a systemd template from which several identical instances can be started, plus a target unit that starts them together. Each worker exits after a fixed runtime or when it reaches a memory limit, in the documentation’s example after 60 seconds (Shopware documentation). The service then starts it fresh. That is intentional: Symfony advises against letting workers run forever and gives the example of a restart after 10 messages (Symfony documentation), because some services accumulate memory over time.

/etc/systemd/system/shopware_consumer@.service
[Unit]
Description=Shopware Message Queue Consumer, instance %i
PartOf=shopware_consumer.target

[Service]
Type=simple
User=www-data
Restart=always
WorkingDirectory=/var/www/html
ExecStart=php /var/www/html/bin/console messenger:consume --time-limit=60 --memory-limit=512M async low_priority

[Install]
WantedBy=shopware_consumer.target
start-workers.sh
# Create the target unit that starts all instances together
cat > /etc/systemd/system/shopware_consumer.target <<'UNIT'
[Install]
WantedBy=multi-user.target

[Unit]
Description=shopware_consumer service
UNIT

# Enable and start the instances
systemctl enable shopware_consumer@{1..3}.service
systemctl enable shopware_consumer.target
systemctl start shopware_consumer.target

# Trigger scheduled tasks separately: an entry in the crontab, not in this script
# */5 * * * * /usr/bin/php /var/www/html/bin/console scheduled-task:run --no-wait

# On every deployment: stop running workers cleanly, the service restarts them
php /var/www/html/bin/console messenger:stop-workers

Don’t forget the failure queue

If a message fails, it moves to the failure queue and is retried automatically 3 times (Shopware documentation); if it fails again, it is deleted. The documentation explicitly advises setting up a CLI worker for this queue as well, otherwise failed messages will not be processed.

How many workers make sense depends on the type of messages. Product indexing usually runs slowly, while other messages are processed quickly (Shopware documentation). From Shopware’s experience, up to three worker processes (Shopware documentation) are normal and useful in production. Anyone who needs more moves the queue out of the database into a dedicated queue system so that the database does not become a bottleneck. Long indexing messages can also be routed to a separate transport so that they do not hold up urgent tasks such as sending mail.

Two points belong in every deployment. Once the new code is in place, messenger:stop-workers ends the running workers cleanly so that they restart with the new version (Symfony documentation). And the switch does not make the server load disappear; it makes it plannable. How to size the PHP process pool for that is described in the article on OPcache, JIT and FPM workers.

The admin search bar searches orders, customers and products. Without further setup, the database answers each of these requests. With small catalogs, this hardly stands out. The documentation considers OpenSearch or Elasticsearch worthwhile once a project contains several thousand data sets (Shopware documentation) and calls it a must-have for large assortments because MySQL does not cope well above a certain assortment size (Shopware documentation).

The admin has its own index, separate from the search in the shop. Since Shopware 6.4.19.0 (Shopware documentation), the admin search also supports queries with AND and OR operators. It is switched on via environment variables, and the index is built with a dedicated command. The documentation advises building the index with es:admin:index in the deployment pipeline instead of having missing indices created on every write, which adds an extra check to every save.

.env.local
# Dedicated OpenSearch index for the admin
ADMIN_OPENSEARCH_URL=localhost:9200
SHOPWARE_ADMIN_ES_ENABLED=1
SHOPWARE_ADMIN_ES_INDEX_PREFIX=sw-admin
SHOPWARE_ADMIN_ES_REFRESH_INDICES=0

# Then, in the deployment pipeline on the console, not in this file:
# bin/console es:admin:index

A newer setting goes one step further: since Shopware 6.7.9.0 (Shopware documentation), OpenSearch can also serve supported lists, filters and searches of the Admin API via ENABLE_OPENSEARCH_FOR_ADMIN_API. The documentation lists this as an experimental feature. For a shop in the Christmas season, that means: test it first on a staging instance with real data, then switch over, not in the week before the campaign starts. How to speed up the search in the shop for customers is covered in the article on fast search suggestions.

The silent fallback to the database

If OpenSearch fails, Shopware silently falls back to the database unless configured otherwise. In large projects, exactly that can completely overload the database; the Performance Tweaks page therefore advises switching this fallback off in the shop via SHOPWARE_ES_THROW_EXCEPTION (Shopware documentation). The Elasticsearch page of the same documentation, by contrast, recommends keeping the exception disabled in production so that search stays available during an outage and expected index errors do not block an update (Shopware documentation). Which setting applies to your shop is a trade-off between database load and availability that should be settled before the Christmas season. For the admin, SHOPWARE_ADMIN_ES_THROW_EXCEPTION mainly serves debugging in local development, according to the documentation.

Lists with many columns and exact totals

In the Christmas season, the order list and the product list are among the views opened most frequently. Their speed depends less on the number of rows on screen than on the query behind them. Every column that shows data from a linked table, such as manufacturer, customer group, shipping method or custom fields, can mean an additional association in the query. Extensions that hook their own columns or filters into lists make the query larger still.

The total count is expensive as well. A list with page numbers needs to know how many hits there are in total. The Admin API offers several modes for this: no total, an exact total, or only the information whether there is a next page. The documentation describes the exact count as performance-intensive and the other two modes as fast (Shopware documentation). For your own lists in extensions and for interfaces that only page through results, the mode “is there a next page” is often enough.

  • Column menu of the list: hide columns that nobody reads in day-to-day work and limit the entries per page to what is needed.
  • In the browser’s network tab, open the request to /api/search/order and check which associations and which count mode are sent along.
  • Deactivate extensions one by one and measure the response time of the same list again, on a staging instance, not in live operation.
  • In your own extensions, load only the fields you need: partial data loading selects fields at database level, while includes only filter after loading (Shopware documentation).

Where a list stays slow despite lean columns, the cause can lie in the database itself: filters on custom fields stored in JSON columns, large tables without cleanup, locks caused by parallel writes. How to find and speed up such queries is shown in the article on database query optimization.

Media and thumbnails

Before campaigns, a particularly large number of images is uploaded: new products, campaign banners, category images. Every upload creates a message for the thumbnails, which is then processed via the queue (Shopware documentation). With the admin worker, the thumbnails are therefore generated in requests from the team’s browser tabs, on the same PHP processes that serve its clicks at the same time. With CLI workers, this runs in separate processes in the background, and image processing no longer occupies the admin’s PHP-FPM processes.

How many thumbnails each upload produces is defined by the media folder: each folder has assigned thumbnail sizes, and generation can be switched off for folders that do not need it (Shopware documentation). Anyone who has to recompute existing images runs media:generate-thumbnails outside working hours. Since Shopware 6.6.4.0 (Shopware documentation), thumbnail generation can also be handed off entirely to an upstream image service that creates the sizes on request; Shopware then no longer creates thumbnail files or records. Which image format pays off there is discussed in the article on JPEG XL in the browser. How the campaign banners themselves reach the storefront without a layout shift is covered in the article on Black Week promo banners.

Indexing and maintenance outside working hours

Some work cannot be avoided, but it can be moved. A full rebuild of all indexes creates messages for every single record, triggered via the command line or via the corresponding button in the admin settings (Shopware documentation). Anyone who starts it on a November morning lengthens the waits in the team until the queue is empty. For the whole shop, the documentation recommends dal:refresh:index with the --use-queue option so that the work runs through the queue; the right time for it is at night.

Then there are scheduled tasks. By default, Shopware generates the sitemap once a day; the list of default tasks gives an interval of 86,400 seconds (Shopware documentation) for it. With large catalogs, this task can occupy the queue for a long time and hold up other tasks. Since Shopware 6.7.1.0 (Shopware documentation), the scheduled sitemap generation can be switched off and started via cron at a quiet hour. The task for product exports appears in the same list with an interval of 60 seconds (Shopware documentation); anyone who maintains many exports checks their individual intervals.

config/packages/prod/shopware.yaml
# config/packages/prod/shopware.yaml
shopware:
    sitemap:
        scheduled_task:
            enabled: false    # sitemap via cron instead of the queue
    product_stream:
        indexing: false       # only after weighing it up, see below
crontab
# Generate the sitemap at night, when nobody works in the admin
0 2 * * * cd /var/www/html && php bin/console sitemap:generate

# Full reindex only when needed, also at night and through the queue
# php bin/console dal:refresh:index --use-queue

Another candidate is the indexer for dynamic product groups. It maintains a mapping of products to their groups and can become a bottleneck with large assortments or frequent changes. Since Shopware 6.6.10.0 (Shopware documentation), it can be switched off. The price: category pages with dynamic product groups only show a product change once the HTTP cache expires, and the rule condition for line items in dynamic product groups then evaluates to “false” throughout. This is a trade-off for shops that maintain a lot and use few dynamic groups, not a default setting.

Small switches with noticeable effect

Besides the big levers, the documentation names a few settings that directly affect the admin. Each one is small on its own; together they take load off the server that nobody consciously notices in day-to-day work.

Mail through the queue

Out of the box, Shopware sends mail synchronously. If the mail server struggles, saving a status change with a notification waits along with it. Sent through the queue, this wait disappears from the admin.

No sample mail per send

For auto-completion in the mail template editor, Shopware writes a sample mail into the database when sending. The update_mail_variables_on_send setting removes this load.

Relieve the increment storage

The increment storage holds the queue status and recently used modules. With several workers, its table is locked often; Redis or switching it off takes the pressure off.

Switch off the APP_URL check

On every admin load, Shopware requests itself to check APP_URL. If the address is correct, APP_URL_CHECK_DISABLED saves this detour.

A large enough OPcache

Shopware loads many classes on every request. The documentation recommends a value of 20,000 (Shopware documentation) or higher for opcache.max_accelerated_files.

Preload with care

According to the documentation, a preload file brings an additional 2 to 5 percent (Shopware documentation), but requires an FPM restart after every cache clear and disables the extension manager.

The mail switch has a big everyday effect for the team: anyone who changes an order status, creates invoices or sends vouchers triggers mail. If it runs synchronously, the response time of the mail server determines how long the admin hangs (Shopware documentation). The increment storage setting has a side effect: switched off, the admin no longer shows queue notifications or the module usage overview. Anyone already running Redis for caching strategies is better off moving the increment storage there.

Measure before you rebuild

Which of these points slows your shop down can only be shown by a measurement during working hours. A measurement on a Sunday evening with a single logged-in person finds neither the admin worker nor the locks that only appear with several workers. What makes sense is a measurement window on a normal working day, with the roles that actually work: customer service, shipping, product maintenance.

  1. In the browser: open the network tab, filter for /api/ and note the slowest requests. Long open requests to message-queue/consume show that the admin worker is at work.
  2. At the PHP process pool: enable the FPM status page via pm.status_path and watch how many processes are active and whether the upper limit is reached (PHP manual).
  3. In the FPM slowlog: set request_slowlog_timeout. By default, the feature is off; once set, FPM writes a backtrace to the slowlog for every request above the threshold (PHP manual).
  4. In the database: the slow query log records queries above long_query_time, which is 10 seconds (MySQL Reference Manual) by default. That is far too coarse for the admin, so set a much lower threshold for the measurement window and reset it afterwards.
  5. On the server: Server-Timing headers show where the time of a request goes in the backend; more on this in the article on Server-Timing.

These values produce a small table per view: order list, product list, search, saving a product, upload. Each row has a baseline before the switch and a value afterwards, measured under comparable load. Anyone who also wants to know before the Christmas season how much concurrent work the server can carry adds a load test that loads the admin and the shop together. A performance analysis provides the baseline for it.

The rule of thumb for the admin

Everything that does not need to wait for the response of the person in front of the screen belongs outside their request: in a CLI worker, in a search index or in the night.

The switchover before the Christmas season

The levers interlock, so a fixed order pays off. The worker comes first because the search index, thumbnails and indexing depend on it: without workers running in the background, the OpenSearch index is not kept up to date either; the documentation explicitly lists them as a requirement (Shopware documentation).

  1. Measure baselines during working hours, per view and role.
  2. Set up CLI workers as a service, include the failure queue and scheduled tasks, then switch off the admin worker.
  3. Send mail through the queue and set the small switches.
  4. Move the admin search to OpenSearch and add the index to the deployment pipeline.
  5. Slim down lists: columns, entries per page, extensions with their own columns.
  6. Move the sitemap, reindexing and image runs to the night.
  7. Measure again under comparable load and record the values.

Nothing is bought in the admin, but everything that is bought is created there. Taking waiting time off the team in the Christmas season frees up time for customer enquiries, shipping and maintenance.

From project work on Shopware performance

A large part of this work is configuration, not reconstruction. What pays off is decided by the measurement: in some shops, switching to CLI workers is enough; in others, extensions in the lists slow things down, or a database that is already tight for the storefront. The storefront has its own levers, from the HTTP cache to theme compilation, summarised in the article Shopware performance: faster loading times. For the admin and storefront together, we offer Shopware performance optimization; for workers, the PHP pool and the database, server optimization.

Sources and documentation

This article is based on data from: Shopware documentation (Message Queue, Performance Tweaks, Set up Elasticsearch, Elasticsearch, Scheduled Task, Increment Storage, Search Criteria, Partial Data Loading, Remote Thumbnail Generation, Add data indexer); Shopware source code (shopware.yaml, ConsumeMessagesController, EarlyReturnMessagesListener, version 6.7.15.0); Nielsen Norman Group 1993 (Response Times: The 3 Important Limits); web.dev (Interaction to Next Paint); Symfony documentation (Messenger, Deploying to Production); PHP manual (FPM configuration); MySQL Reference Manual 8.4 (The Slow Query Log).

Related Articles

Core Web Vitals & measurement

Adding Black Week Promo Banners Without Layout Shift

Add Black Week notice bars, countdowns, pop-ups and badges without anything jumping: reserve space, deliver from the server and keep CLS at 0.1 or below.

20 min read
Front-end optimisation

JPEG XL Is Back in Browsers: Is It Worth It for Shops?

Chrome 155 and Firefox 158 decode JPEG XL. When the format pays off next to AVIF and WebP for shops, and how to repack JPEGs losslessly and serve them safely.

17 min read
Front-end optimisation

Yesterday's speed tricks: what slows sites down on HTTP/2

Domain sharding, bundles, sprites and data URIs date from the HTTP/1.1 era. How to spot them in the waterfall and roll them back safely under HTTP/2.

19 min read