Shopware Installation 2026: A Deep Dive With Real Measurements

Shopware Installation 2026: A Deep Dive With Real Measurements

A Shopware installation does not take long. That is the good news, and it is verifiable: for this article we installed Shopware 6.7.13.1 from scratch three times in one evening, stopwatch and measuring tape in hand. The headline numbers first — 17.5 seconds to download the project with a completely empty cache, 27.3 seconds for the actual installation including 848 database migrations. Under a minute in total.

The bad news is not in the documentation: both installation paths we tested ended with a success message and a shop that still did not work on first request. No crash. No red error page. A silent HTTP 400 and a politely styled page titled “Sales Channel Not Found”. And in our Composer installation the administrator login was broken on top of that, while system:install had explicitly reported [OK].

That is what this article is about. Not retyping the official commands — those are in the Shopware docs and they are correct there. It is about what happens between “command issued” and “shop actually running”, with real numbers from a real run.

The short version

measured value
Version tested6.7.13.1 (released 25 Aug 2026)
composer create-project, cold cache17.5 s
composer create-project, warm cache19.6 s
Cache data downloaded84 MB
Project size after download332 MB, 33,720 files
PHP files in vendor/17,300
Composer packages164
system:install --basic-setup27.3 s, peak memory 225 MB
Migrations executed848
Tables created252
Database size right after install14.5 MB (7.1 MB data, 7.3 MB indexes)
Products / customers / orders afterwards0 / 0 / 0
Storefront, first request (cold cache)2.39 s
Storefront afterwards (median of 20 runs)9 ms
dockware container until HTTP 20013.9 s
Method: all values collected on 29 Aug 2026 on an Ubuntu 24.04 server (12 vCPU, 22 GB RAM, NVMe). PHP 8.3.6 CLI, MariaDB 10.11.14, Composer 2.10.3. Timings via /usr/bin/time -f "%e", memory as MAXRSS. Database sizes from information_schema.tables. HTTP timings with curl -w "%{time_total}" against PHP’s built-in web server — deliberately not a production stack, so the ratios are meaningful but the absolute values do not transfer to live systems.

The most important number in that table is the one that is not there: after both successful installations, the time until the first working storefront request was not 27 seconds but “however long it takes to understand why you are getting a 400”.

The three paths — and what each is actually for

Three parallel installation routes lead to the same destination: container, command line and unpacked archive

Shopware has essentially three installation paths, and the confusion starts with them being presented online as equivalent alternatives. They are not — they have different audiences.

1. Composer (shopware/production). The path for anything that will become a real project. You get a directory with a composer.json you can version, and from that point on your shop installation is a reproducible software project. This is the path we used for the measurements.

2. dockware (Docker). A prebuilt container image with Apache, MySQL, PHP, Adminer and Mailcatcher — all preconfigured and preinstalled. Intended for local development and for trying things out. 13.9 seconds from docker run to a responding shop, including 16 demo products.

3. The ZIP archive from shopware.com. The classic “upload and click through in the browser” path. It still exists and is aimed at people on shared hosting without shell access.

One detail many guides get wrong: there are no ready-made installation packages on GitHub. We queried the release data of shopware/shopware — release v6.7.13.1 has zero assets. What lives there is the source of the development monorepo, not an installable package. Anyone describing “download Shopware from GitHub” as an installation path is talking about a different procedure.

Which path should I pick?

SituationPath
New client project, Git, CI/CDComposer
Just having a look, locally, todaydockware
Plugin development, several versions side by sidedockware
Shared hosting without SSHZIP archive
Updating an existing projectComposer (not a reinstall)

System requirements: what is actually checked

A checklist of server prerequisites as a vertical stack with checkmarks and one warning

Instead of copying the requirements from the documentation, we read them straight out of the installed core’s composer.json — that is the file Composer actually validates against during installation.

PHP versions according to the core: ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0. So Shopware 6.7 runs from PHP 8.2 through 8.5. Anyone on PHP 8.1 will not get past Composer — with a clear error message, which is the pleasant kind of failure.

Required extensions: 20 of them. Read out and checked against our installation:

ctype, curl, dom, fileinfo, filter, gd, intl, json, libxml, mbstring, openssl, pdo, pdo_mysql, session, simplexml, sodium, xml, xmlreader, zip, zlib

All 20 were present on our system — which is simply because it is a well-maintained web server. On a fresh minimal install, intl, gd and sodium are usually the first ones missing. You can check this in one line before you even start:

php -r 'foreach(["ctype","curl","dom","fileinfo","filter","gd","intl","json","libxml","mbstring","openssl","pdo","pdo_mysql","session","simplexml","sodium","xml","xmlreader","zip","zlib"] as $e) if(!extension_loaded($e)) echo "MISSING: $e\n";'

Database. We installed against MariaDB 10.11.14 with no special configuration whatsoever — and it worked. That is worth mentioning because a lot has been written online about MySQL tuning parameters being required. For the installation, that is not true. Our values during setup: innodb_buffer_pool_size 128 MB (the default, not a production recommendation), max_allowed_packet 16 MB, group_concat_max_len 1,048,576. The installation completed without a single warning.

Memory. Peak usage of system:install was 225 MB RSS. A memory_limit of 512 MB is therefore comfortable, and 256 MB was enough in our run too. The common advice to “just set it to -1” is not necessary for installation — it only hides the fact that nobody measured.

Path 1: Composer, measured step by step

composer create-project shopware/production:6.7.13.1 sw67 --no-interaction

That was the entire download. 17.5 seconds with a completely empty cache directory, 19.6 seconds with a warm cache.

That ordering gave us pause — the cold run was faster than the warm one. Which is exactly why a single measurement is worthless: the 2.1-second difference sits inside the noise of network and Composer’s parallel jobs, it is not a cache effect. We are writing this down rather than dropping the inconvenient number. Anyone reading a trend out of two runs is guessing.

What ends up on disk afterwards:

value
Project size332 MB
Total files33,720
PHP files in vendor/17,300
Composer packages164
Composer cache afterwards84 MB
Method: du -sh on the project directory, find . -type f | wc -l for the file count, composer show | wc -l for packages. Cache size from the empty cache directory created for the cold run.

332 MB for an empty shop system is a number worth knowing before you plan three instances on a 10 GB VPS. For comparison: the dockware image carries 592 MB internally, 529 MB of that in vendor/ alone, because the development dependencies live there as well.

Also notable in the output was the final line: “No security vulnerability advisories found.” Composer now checks the result against the vulnerability database automatically. On a fresh install of the latest version that is expected — on a three-year-old project it is the single most useful line of the whole operation.

The database connection

Before installation, Shopware needs a .env.local. The stumbling block hides in the line that looks most harmless:

DATABASE_URL="mysql://sw67:SwTest2026%21@127.0.0.1:3306/sw67_test"

The %21 at the end of the password is an encoded exclamation mark. DATABASE_URL is a URL, and special characters in the password must be percent-encoded — otherwise the connection breaks in a way that looks like a database problem when it is a parsing problem. Affected characters include @ : / ? # [ ] ! $ & ' ( ) * + , ; =.

Also deliberate: 127.0.0.1 instead of localhost. With localhost, PHP attempts a Unix socket connection; with 127.0.0.1 it uses TCP. On systems whose socket path differs from the PHP configuration, that is precisely the difference between “works” and “connection refused” — with an identical database.

The installation itself

php bin/console system:install --basic-setup --create-database --force --no-interaction

27.3 seconds. We counted along from a second shell during that half minute: after 24 seconds of runtime, 244 tables already existed, shortly followed by 252. So migrations do not run evenly — the bulk of the schema appears in a single block early on.

A database being filled from above with structured blocks, next to a progress ring

The end result:

value
Tables252
Rows in migration848
Database size14.5 MB
data / indexes7.1 MB / 7.3 MB

The number that surprised us: the indexes are larger than the data. 7.3 against 7.1 MB. On an empty shop that is not a coincidence but by design — Shopware creates the full index schema regardless of content. The practical consequence for backups and migrations: a shop’s database size says very little about its content volume unless you separate data from indexes.

What --basic-setup does not do: create demo data. After installation our database held 0 products, 0 customers, 0 orders — but 2 sales channels and 848 migration records. The dockware image, by contrast, ships with 16 products and 1 customer. Anyone testing an installation should know which of those two starting points they are looking at before being puzzled by an empty shop.

Failure one: “[OK]” with a broken login

A request bounces off an invisible barrier while the gate itself looks intact

system:install dutifully reported [OK] Successfully copied all bundle files and [OK] Cache ... successfully cleared. Exit code 0. All green.

Then the first API login:

{"errors":[{"code":"0","status":"500","title":"Internal Server Error",
"detail":"Key provided is shorter than 256 bits, ..."}]}

Cause: our APP_SECRET was 29 characters = 232 bits. The threshold is 256 bits, i.e. 32 characters. We were three characters short.

The bitter part is not the mistake — we caused it ourselves by typing a placeholder for the test. The bitter part is when it becomes visible. The installer does not check the length. The schema is created, the bundles are copied, the cache is built, everything reports success — and it only falls over when a human first tries to log into the administration. On a client system that typically happens days later, with maximum distance between cause and symptom.

The underlying rule, which goes well beyond Shopware: an installer that merely stores configuration instead of validating it pushes the failure into the future and makes it more expensive on the way. The fix took us eight seconds:

# 32 bytes = 64 hex characters = 512 bits, comfortably above the threshold
NEW=$(openssl rand -hex 32)
sed -i "s/^APP_SECRET=.*/APP_SECRET=$NEW/" .env.local
php bin/console cache:clear

Afterwards the same call returned a 419-character token and GET /api/_info/version answered with {"version":"6.7.13.1"}. So we did not “probably fix it” — we fetched the proof that had been missing.

Important for anyone setting their own secret: openssl rand -hex 32 produces 64 characters. The tempting shortcut openssl rand -hex 16 produces 32 characters — which look exactly right, but sit precisely on the edge of the bit-level HMAC check. Take the 64.

Failure two: HTTP 400 instead of a homepage

The shop was running. The API answered. And requesting the homepage returned:

HTTP/1.1 400 Bad Request
<title>Sales Channel Not Found</title>

No crash. No 500. No entry that looks like an emergency. A designed page politely explaining that it does not recognise the sales channel.

The cause sits in a single table:

SELECT url FROM sales_channel_domain;
-- default.headless0
-- http://localhost:8099

Shopware maps an incoming request to a sales channel using the Host header. Our curl addressed 127.0.0.1:8099 — the database said localhost:8099. To a human that is the same machine. To the mapping they are two different domains, and neither of them is called 127.0.0.1.

The proof in three lines:

Host addressedResponse
127.0.0.1:8099400 Sales Channel Not Found
localhost:8099 (Host header)200, 78,031 bytes of storefront

And the exact same failure reappeared on the second installation path. The dockware container also answered http://127.0.0.1:8100/ with 400 — even though the image ships fully configured and its own log cheerfully states SHOP URL: http://localhost. With Host: localhost a 200 with a complete storefront came back immediately.

Host header sent to dockwareResponse
localhost200 (1.90 s, cold)
localhost:8100400
127.0.0.1:8100400

That is the real finding of the evening: two completely different installation paths, the same trap, and in both cases the result does not look like a configuration error but like a broken shop. A first-time installer will look for the fault in the database, the cache or the file permissions — in other words, everywhere it is not.

Also interesting: localhost worked for the container, localhost:8100 did not. The port is part of the stored domain. Since dockware listens on port 80 internally and we mapped 8100 from outside, only the port-less variant matches.

Diagnosis in one line

mysql -e "SELECT url FROM sales_channel_domain;" YOUR_DB

Whatever is in there must match exactly what the browser puts in the address bar — including http/https, including www, including the port. You can change it in the administration under Sales Channels → Domains, or during setup with --shop-url.

Path 2: dockware in comparison

For local development the container path is hard to beat:

docker run -d --name shop -p 127.0.0.1:8100:80 dockware/dev:6.7.2.2

13.9 seconds from issuing the command to the first HTTP 200 — measured as a polling loop at two-second intervals, with the hit landing on the fifth attempt. That includes Apache, MySQL, PHP 8.3.25, Adminer, Mailcatcher, an installed shop with 236 tables, a 12.9 MB database and 16 demo products.

One detail we want to underline from our own practice: the -p 127.0.0.1:8100:80 is not cosmetic, it is mandatory. The common shorthand -p 8100:80 binds the port to 0.0.0.0 — meaning every network interface on the server, including the public one. We have paid for exactly this mistake before: a PostgreSQL port bound to the open internet by Docker default, which earned us a report from the German federal cyber security agency. How to set up a server so that things like this surface early is covered in Linux server setup and IT security vulnerabilities.

Composer or container?

Composerdockware
Time to running~45 s (17.5 + 27.3)13.9 s
Disk footprint332 MB + DB~1.8 GB image
Demo datanone16 products, 1 customer
Databasebring your ownincluded
Versionable (Git)yesonly the project inside
Production-readyyesno (dev image)
Switching versionsComposer constraintdifferent image tag

The honest recommendation: dockware for learning, experimenting and plugin development. Composer for anything that will ever go live. And anyone using both in parallel should know the versions drift apart — our container ran 6.7.2.2 with 236 tables, the Composer install ran 6.7.13.1 with 252. Eleven patch releases meant 16 additional tables here.

Performance right after installation

After setup we measured the storefront — deliberately against PHP’s built-in web server, so without nginx, without OPcache tuning, without HTTP cache. The absolute values therefore do not transfer to production, but the ratio very much does:

StateTime
First request after cache:clear2.39 s
Second request53 ms
Third request50 ms
Median of 20 warm runs9 ms
p95 of 20 warm runs12 ms
Method: curl -w "%{time_total}" against php -S, Host header localhost:8099, storefront homepage (78,031 bytes). Cache directory cleared between series with rm -rf var/cache/prod_*.

A factor of 265 between the first and the typical request. That is why a freshly deployed Shopware installation feels slow and stops feeling slow ten minutes later — and why a performance verdict formed after the first click is worthless. Anyone measuring after a deployment has to warm the cache first, or they are measuring container compilation rather than page delivery.

The same applies to live measurement with third-party tools: how to measure web performance so the number actually means something is what we built into our Core Web Vitals test.

After installation: what is still missing

A fresh installation surrounded by concentric protective rings: cache, security, backup

A successful installation is not yet a shop. The points still open in our run:

1. APP_ENV=prod. The default in the container setup is dev — dockware explicitly ran with (env: dev, debug: true) for us. On a publicly reachable system that is information disclosure, not a convenience feature.

2. A real web server. php -S is a test tool, not a server. Production needs nginx or Apache with PHP-FPM — the fundamentals are in nginx as a reverse proxy and in our Apache vs nginx comparison.

3. HTTPS. No TLS means no secure session, and mixing protocols in the sales channel domain entry lands you right back at the 400 above.

4. Tuning the database. Our innodb_buffer_pool_size sat at the default 128 MB. Fine for installation, nowhere near enough for a shop with a real catalogue.

5. The admin user. --basic-setup creates admin with the password shopware. Convenient and publicly documented — therefore convenient for everyone else too.

6. Backups before there is data. The best moment to practise restoring is the one where nothing can break yet.

What the installation tells you about cost

One thing you rarely read in an installation guide: the installation procedure itself is identical across all editions. What we installed here is the Community Edition — open source, no licence fee, built on the same core the commercial plans sit on.

Costs do not arise at installation but afterwards: hosting, plugins, agency time, maintenance. We have worked through that elsewhere with real numbers — in What does Shopware 6 cost? and Shopware store costs. If you are still deciding whether to self-host at all, the architecture question is in Shopware Cloud vs self-hosted, the platform comparison in Shopify vs Shopware, and the wider overview in best shop system.

Frequently asked questions

How long does a Shopware installation take?

In our measurement on 29 Aug 2026: 17.5 seconds for composer create-project and 27.3 seconds for system:install, so under a minute on a server with 12 vCPU and NVMe storage. Via dockware it was 13.9 seconds to the first HTTP 200. On weaker hardware and slower connections it takes longer, but the order of magnitude stays minutes, not hours.

Which PHP version do I need for Shopware 6.7?

The core’s composer.json allows ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0, so PHP 8.2 through 8.5. We installed with PHP 8.3.6. Older versions are rejected by Composer at download time with an unambiguous error message.

Which PHP extensions does Shopware require?

Twenty: ctype, curl, dom, fileinfo, filter, gd, intl, json, libxml, mbstring, openssl, pdo, pdo_mysql, session, simplexml, sodium, xml, xmlreader, zip, zlib. The ones most often missing on fresh servers are intl, gd and sodium.

How much disk space does a Shopware installation need?

After composer create-project there were 332 MB in 33,720 files on disk, plus 14.5 MB of database right after setup. The Composer cache took another 84 MB. The dockware development image carries 592 MB internally, 529 MB of that in the vendor/ directory.

Why does my freshly installed shop show “Sales Channel Not Found”?

Because the host you requested does not match any entry in the sales_channel_domain table. Shopware maps requests via the Host header — 127.0.0.1:8099 and localhost:8099 are two different domains as far as that mapping is concerned. Check with SELECT url FROM sales_channel_domain; and adjust the entry to the address you actually use. The error returns HTTP 400, not a 500, which is why it does not look like a configuration problem.

Why does the admin login fail after installation?

A common cause is an APP_SECRET that is too short. Shopware requires at least 256 bits, i.e. 32 characters. Ours was 29 characters (232 bits) — the installation still reported success, and only the OAuth call failed with “Key provided is shorter than 256 bits”. Fix: openssl rand -hex 32 produces 64 characters, then run cache:clear.

How many database tables does Shopware 6.7 create?

In our installation of 6.7.13.1 it was 252 tables and 848 migration records. The dockware image running 6.7.2.2 had 236 tables — so the number grows with every release.

Does the installation create demo data?

system:install --basic-setup does not: after our run the database held 0 products, 0 customers and 0 orders, but 2 sales channels. The dockware development image, by contrast, ships with 16 demo products and one demo customer.

Can I download Shopware from GitHub and install it?

Not as a ready-made package. Release v6.7.13.1 in the shopware/shopware repository has no release assets — what lives there is the source of the development monorepo. For an installation, use composer create-project shopware/production or the ZIP archive from shopware.com.

Why is the first page request so slow?

Because the Symfony container and the Twig templates are compiled on first access. For us the first request after cache:clear took 2.39 seconds, while the median of the following 20 requests was 9 milliseconds — a factor of 265. After every deployment the cache should therefore be warm before real visitors hit it.

Conclusion

A Shopware installation is technically unspectacular: two commands, under a minute, 332 MB on disk, 252 tables in the database. Check the 20 extensions beforehand and encode the password in DATABASE_URL correctly, and you will get through without incident.

The real effort sits elsewhere — and that is the lesson of this evening. Both installation paths ended with a success message and a shop that returned HTTP 400 on first request. In one case the administrator login was broken on top of that, while the installer had explicitly reported [OK]. Neither of those states produces a crash, a stack trace or a log entry that looks like an emergency. They look like a broken system and are two lines of configuration.

Knowing that costs you ten minutes. Not knowing it costs you an evening spent searching the cache, the file permissions and the database — all the places where the fault is not.