Installation and Local Environments

This content was generated with the assistance of AI and should be verified against the official documentation of each platform before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page covers every way to get a PrestaShop 9.2.0 shop running, from an empty server or an empty Docker host to a shop you have secured, and how to move that shop later. It was written against PrestaShop 9.2.0 (released 2026-09-30) and checked on 2026-10-27 with the official prestashop/prestashop Docker images (PHP 8.5.12, Apache 2.4.68), MySQL 8.4.13, Nginx (stable) and a command-line install on a second, throwaway shop. It applies to the open source software on your own hardware or hosting; it does not cover hosted PrestaShop offerings. Read Versions and Support first if you have not picked a version.

The page follows the order you meet the problems in: requirements, the three installation paths (web installer, command line and Docker), the web server, the steps after the install that matter for security, and moving a shop to a new server or domain. The running example, Pocket Coffee, is installed with the command-line installer for a Spain shop with Spanish and English storefronts, and with Docker Compose for local development.

The web installer, the command-line installer and the official Docker image all lead to the same installer code, followed by the post-install steps

Requirements

PrestaShop 9 needs a web server, PHP and a database. The official requirements, as of 9.2:

Requirement Configuration

Web server

Apache 2.4 or later (2.2 is not recommended). Nginx 1.0 or later also works with extra configuration, see Nginx. PrestaShop works best on Unix-based systems such as Linux and macOS; Windows might work but is not actively supported.

PHP

8.1 to 8.5, with 8.5 recommended for 9.1 and 9.2. The installer of 9.2.0 refuses to run on a PHP version outside that range.

Database

MySQL 5.7 or MariaDB 10.2 minimum; the documentation recommends a recent version. The installer does not check the version. Create the database with the utf8mb4_general_ci collation.

Memory

memory_limit of 512M or more per script.

PHP extensions and settings

The following PHP extensions are required: curl (download of modules and localisation packs), dom, fileinfo, gd (thumbnails), iconv, intl (localised amounts), json, mbstring, openssl, PDO, pdo_mysql, SimpleXML and zip (expanding modules and packs). allow_url_fopen must be On, because PrestaShop reads remote files, among other things during payment processing; allow_url_include should stay Off.

On a host you can check them with the project’s phppsinfo.php tool (download it from the PrestaShop/php-ps-info repository, upload it next to the shop and open it in a browser; the documentation says the login and password are prestashop, so remove it afterwards). In a container or on the command line, ask PHP directly.

docker exec prestashop php -r 'foreach (["curl","dom","fileinfo","gd","iconv","intl","json","mbstring","openssl","PDO","pdo_mysql","SimpleXML","zip"] as $e) { echo str_pad($e, 12), extension_loaded($e) ? "ok" : "MISSING", PHP_EOL; } echo "allow_url_fopen=", ini_get("allow_url_fopen") ? "On" : "Off", PHP_EOL;'

All thirteen extensions printed ok in the official image, and allow_url_fopen=On and allow_url_include=Off were the settings; the image also ships bcmath, soap, sodium and OPcache, and a CLI memory_limit of 512M. Replace prestashop with the name of your own container, or drop docker exec prestashop to run the check on a host.

Creating the database

The installer can create the database (--db_create=1 on the command line) but it then uses the server default collation. On the MySQL 8.4 test server that default is utf8mb4_0900_ai_ci, while the PrestaShop documentation asks for utf8mb4_general_ci. Create the database yourself with the collation the documentation names, and give the shop a dedicated user that owns that database only.

CREATE DATABASE pocketcoffee CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'pocketcoffee'@'%' IDENTIFIED BY '<choose-a-strong-password>';
GRANT ALL PRIVILEGES ON pocketcoffee.* TO 'pocketcoffee'@'%';
FLUSH PRIVILEGES;

Run on MySQL 8.4.13 these statements succeeded, and information_schema.schemata then showed pocketcoffee with utf8mb4 and utf8mb4_general_ci. Replace the host part % with the host the web server connects from on a real server (for example localhost); it was % here because the database ran in another container. With MySQL 8 and a mysql client or PHP driver old enough not to know caching_sha2_password or the utf8mb4 charset, the official Docker README lists workarounds (force mysql_native_password, change the server charset); the PHP of the 9.2.0 image connected to MySQL 8.4 without any of them.

Choosing an installation path

Path Use it for Notes

Release package and web installer

A shop on shared or managed hosting, or a first look.

Upload the single zip distributed on prestashop.com (GitHub carries no release zip for 9.x), open the installer in a browser and answer seven steps.

Command-line installer index_cli.php

Scripted, repeatable installs on a server you can reach by SSH, CI jobs and staging copies.

Every web installer option is an argument. Nothing to click.

Official Docker image

Local development, tests, throwaway environments and some production setups.

PS_INSTALL_AUTO=1 runs the command-line installer for you. Needs a volume on /var/www/html and a database container.

PrestaShop Flashlight

Development and testing with extra tools in the containers.

A separate project of the PrestaShop organisation with docker compose examples; not for production.

AMP stack on your own machine (XAMPP, MAMP, WampServer, a Linux LAMP)

Development without Docker.

You install Apache, MySQL and PHP yourself, then use the web or command-line installer.

Whichever path you take, the result is the same: files in a folder, an app/config/parameters.php with the database settings and a set of keys, and a database with the tables, the default data, the languages, the theme and the modules you selected.

The release package and the web installer

PrestaShop 9 is distributed as a single zip on prestashop.com. In the zip you find index.php and prestashop.zip (plus Install_PrestaShop.html). The official image carries the same payload: its /tmp/data-ps folder holds Install_PrestaShop.html, index.php and the unpacked prestashop folder, and the shop’s INSTALL.txt describes the upload routine: upload index.php and prestashop.zip to the server, browse to index.php (it unzips the archive and redirects to the installer), follow the installer and delete the install folder afterwards. As of 9.2 the installer deletes that folder itself.

The developer version (a clone of the Git repository, or the develop branch for contributors) is different: it needs composer install for the PHP dependencies and make assets with Node.js 20.x to compile the JavaScript and CSS, and its installer lives in install-dev rather than install. Use it only if you work on PrestaShop itself.

File permissions

PrestaShop needs recursive write permission on these folders: admin-dev/autoupgrade (an autoupgrade folder inside the admin folder), app/config, cache, config, download, img, log, mails, modules, override, themes, translations, upload and var. The files must belong to the user that runs PHP (www-data on Debian and Ubuntu and in the official image). The documentation’s development shortcut, running Apache as your own user, must never be used in production: set owners and modes folder by folder instead, and avoid mode 777.

chown -R www-data:www-data /var/www/html
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;

The three commands are the usual starting point for a host install (the official image already does this); they were not run on a host for these pages. In the image every file of the shop belongs to www-data; the folders checked (var, app/config, img, modules and themes) have mode 755 and parameters.php and .htaccess mode 644.

The seven steps

Opening the shop before it is installed redirects to the installer (the test container answered 302 to /install/). The installer lists seven steps, which are the strings the installer itself displays:

  1. Choose your language: the language of the installer and the default language of the shop.

  2. License agreements: the licence terms (the core is licensed under OSL 3.0, the modules and themes under AFL 3.0), a checkbox to accept them and a privacy note.

  3. System compatibility: the PHP version, the ability to upload files and create files and folders, the required extensions and the Symfony requirements listed in Requirements; the installer cannot continue until the required tests pass.

  4. Store information: the store name, country, shop timezone, an Enable SSL choice, and the administrator account (first name, last name, e-mail address, which is the back-office user name, and a password of at least eight characters that must reach a minimum strength score, typed twice).

  5. Content of your store: the theme to install, whether to install the demo products (recommended only while you are learning PrestaShop), and whether to install all modules (recommended) or a selection.

  6. System configuration: the database server address (add :port for a port other than 3306), name, login, password, tables prefix, the "Drop existing tables" choice and a button that tests the connection. Change the default prestashop database name and ps_ prefix to something specific to your project.

  7. Store installation: the progress page. When it finishes it shows the login information, a reminder to delete the install folder (the 9.2 installer has already removed it) and links to the back office and the front office.

The seven titles were read from the installer page of the 9.2.0 image and the field descriptions of steps 2 to 7 from the installer’s own view templates; the steps were not clicked through in a browser, because the command-line installer runs the same code and is easier to repeat.

The command-line installer

install/index_cli.php (install-dev/index_cli.php on a development checkout) runs the same installation without a browser. It has existed since PrestaShop 1.5.4. Start it as the web server user from the install folder.

cd /var/www/html/install
php index_cli.php \
  --domain=pocketcoffee.example.test \
  --db_server=127.0.0.1:3306 \
  --db_name=pocketcoffee \
  --db_user=pocketcoffee \
  --db_password="${DB_PASSWORD}" \
  --prefix=pcf_ \
  --name="Pocket Coffee" \
  --country=es \
  --language=en \
  --timezone=Europe/Madrid \
  --email=owner@example.test \
  --password="${ADMIN_PASSWORD}" \
  --firstname=Ada --lastname=Lovelace \
  --fixtures=0

Run with those options (the test values localhost:8095, a throwaway database user, --db_create=1 and prefix pcfa_) on a fresh container it printed, in order, "Installing database", "Dropping database tables", "Installing default data", "Creating shop", "Installing languages: en, es", "Populating database", "Configuring shop", "Installing modules on disk", "Installing theme hummingbird", the line telling that the admin folder was renamed (to admin867rtqhwnkf2hocbvsx in that run), "The install/ folder was removed." and finally -- Installation successful! --, in about 34 seconds. The front office then answered 200, the shop had 80 modules, no products (--fixtures=0), the timezone Europe/Madrid and two active languages: choosing --country=es added Spanish next to the English default.

Arguments

Argument Meaning Default

step

Which steps to run: all, or a comma-separated list of database, fixtures, theme, modules, postInstall.

all

language

Language ISO code of the default language (a 639-1 code with a translation file in translations).

en

all_languages

Install every available language.

0

timezone

Shop timezone from the tz database.

Europe/Paris

base_uri

Base URI appended after the domain, for a shop in a sub-folder.

/

domain

Domain name of the shop, without the protocol; add :port for a non-standard port.

localhost

db_server

Database host. Add :port for a port other than 3306, for example sql.example.test:3307.

localhost

db_user, db_password

Database credentials.

root and an empty password

db_name

Database name. The documentation strongly recommends changing the default.

prestashop

db_clear

Drop the existing tables before installing.

1

db_create

Create the database if it does not exist.

0

prefix

Table prefix. The documentation strongly recommends changing the default.

ps_

engine

MySQL engine, InnoDB or MyISAM.

InnoDB

name

Shop name.

PrestaShop

country

Shop country, an ISO 3166 alpha-2 code.

fr

firstname, lastname

Administrator name.

John, Doe

email, password

Administrator e-mail and password.

pub@prestashop.com and a built-in password, see below

license

Show the licence after the installation.

0

theme

Theme to install, from themes/. Since 9.1 an empty value installs Hummingbird.

empty

ssl

Enable SSL.

0

rewrite

Enable the rewrite engine (friendly URLs).

1

fixtures

Install the demo products, the "Installation of demo products" option of the web installer.

1

modules

Modules to install, comma separated, from modules/.

all

The Docker entrypoint also passes --newsletter and --send_email, which exist in the installer but are not listed by the built-in help.

Pitfalls of the command-line installer

  • The default administrator password is not the one in the documentation. The online table lists "Correct Horse Battery Staple"; the source of the 9.2.0 installer (and its built-in help, printed by running php index_cli.php without arguments) says Pr3st4Sh0P. Always pass --password, and change it after the install: the console command prestashop:employee:change-password is new in 9.2.

  • Arguments are visible. A password given on the command line appears in the process list and in the shell history. Install with a throwaway password and change it, or run the installer from a protected deployment script.

  • The database must exist unless you pass --db_create=1, and --db_clear=1 is the default, so running the installer twice against the same database erases the first shop.

  • The printed back-office address has a doubled slash (http://localhost:8095/admin867rtqhwnkf2hocbvsx//), which is harmless; remove one slash. Find the folder name with ls at any time.

  • PHP outside 8.1 to 8.5 stops the installer with the message "Your server is running PHP x, but PrestaShop requires a PHP version between PHP 8.1 and PHP 8.5." (a message printed by install/index_cli.php itself).

  • The CLI installer needs memory. The Docker entrypoint runs it with php -d memory_limit=-1; if your CLI memory_limit is low, the install can die in the middle of the module step.

The official Docker image

The prestashop/prestashop image (source repository PrestaShop/docker) packages PHP, Apache or PHP-FPM, and a release of PrestaShop. It does not contain a database: you run MySQL or MariaDB next to it. On first start its entrypoint script copies the shop from /tmp/data-ps/prestashop into /var/www/html, optionally renames the install and admin folders, and, when PS_INSTALL_AUTO=1, runs the command-line installer with arguments built from environment variables. On later starts it finds app/config/parameters.php and only prints "PrestaShop Core already installed".

This image is built for one host. Turning it into an immutable image that several pods share, with a managed database outside the cluster, is covered in Cloud: Container Image and Cloud: Reference Architecture.

Environment variables

These are the variables the 9.2.0 image defines, with their defaults as printed by docker run --entrypoint env and as used by the entrypoint script.

Variable Default in the image Effect

PS_INSTALL_AUTO

0

1 runs the installer on first start. Needs DB_SERVER.

DB_SERVER

unset (<to be defined>)

Database host. The installer is given DB_SERVER:DB_PORT.

DB_PORT

3306

Database port.

DB_NAME, DB_USER, DB_PASSWD, DB_PREFIX

prestashop, root, admin, ps_

Database name, user, password and table prefix. Change all four, especially the password.

PS_DOMAIN

unset (<to be defined>)

Domain, with port, under which the shop is reached from the browser. When unset the entrypoint uses the container’s own IP address (hostname -i).

PS_LANGUAGE, PS_COUNTRY, PS_ALL_LANGUAGES

en, GB, 0

Default language, country and whether to install every language.

ADMIN_MAIL, ADMIN_PASSWD

demo@prestashop.com, prestashop_demo

First administrator. The defaults are public; always override them.

PS_ENABLE_SSL

0

Enable SSL at install time.

PS_DEV_MODE

0

Read with a PHP boolean cast by config/defines_custom.inc.php, which then defines _PS_MODE_DEV_: 0 and an empty value are off, any other string is on. PS_DEV_MODE=false therefore enables developer mode; use 0 or leave it unset.

PS_DEMO_MODE

0

Defines _PS_MODE_DEMO_, for demonstration shops.

PS_HOST_MODE

0

Defines _PS_HOST_MODE_, which simulates a PrestaShop Cloud environment.

PS_FOLDER_ADMIN, PS_FOLDER_INSTALL

admin, install

Rename the folders before the install. Do not change PS_FOLDER_ADMIN, see the pitfalls below.

PS_ERASE_DB, PS_INSTALL_DB

0, 0

Drop, or create, the database before installing.

PS_HANDLE_DYNAMIC_DOMAIN

0

For shops whose IP or port changes between runs: the entrypoint adds docker_updt_ps_domains.php as the first directory index so that the domain stored in the database follows the address of the request.

The defaults above are what the image does. The documentation’s table disagrees with the image on several lines, which causes real mistakes: it lists PS_INSTALL_AUTO as 1, PS_FOLDER_ADMIN as admin-dev, PS_FOLDER_INSTALL as install-dev, PS_COUNTRY as fr, DB_SERVER as mysql, DB_PASSWD as prestashop, ADMIN_PASSWD as "Correct Horse Battery Staple" and PS_DEV_MODE as 1. In the 9.2.0 image the values are those in the table above. In particular the image does not start in developer mode unless you ask for it.

Docker Compose

The following Compose file starts MySQL 8.4 and PrestaShop 9.2.0, installs the shop automatically for the domain localhost:8096, keeps the database and the shop files in volumes, and waits for MySQL to be healthy before starting the shop. The three passwords come from a .env file next to it, which you create with your own values and never commit.

services:
  db:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: prestashop
      MYSQL_USER: prestashop
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - dbdata:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 5s
      timeout: 5s
      retries: 20
  prestashop:
    image: prestashop/prestashop:9.2.0
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "8096:80"
    environment:
      DB_SERVER: db
      DB_NAME: prestashop
      DB_USER: prestashop
      DB_PASSWD: ${MYSQL_PASSWORD}
      PS_INSTALL_AUTO: "1"
      PS_DOMAIN: localhost:8096
      PS_LANGUAGE: en
      PS_COUNTRY: ES
      ADMIN_MAIL: owner@example.test
      ADMIN_PASSWD: ${ADMIN_PASSWD}
      PS_DEV_MODE: "0"
    volumes:
      - psdata:/var/www/html
volumes:
  dbdata:
  psdata:
docker compose up -d
docker compose logs -f prestashop

This file was run with docker compose up -d. docker compose config -q accepted it, MySQL became healthy, and the shop container logged the installer messages ending with "Installation successful!". The front office answered 200 on http://localhost:8096/ with the demo products and the back office was in the folder printed by the installer (admin270x0zcwllwpnqk6aw1 in that run). The container showed a restart count of 1, for the reason below.

What the entrypoint does, and the problems it has

The entrypoint of the 9.2.0 image behaves as follows, and three of its behaviours surprised the test runs.

  1. It creates an install.lock file in /var/www/html, copies the shop from /tmp/data-ps/prestashop (without overwriting anything that exists), applies defines_custom.inc.php, runs any scripts in /tmp/pre-install-scripts/, renames the install and admin folders if the variables differ from the defaults, and runs the installer.

  2. After a successful install it runs rm -r /var/www/html/install/. Since PrestaShop 9.2 the installer has already removed that folder, so rm fails with "No such file or directory" and the script, which runs with set -e, exits with status 1 before it removes install.lock and before it starts Apache. The shop is completely installed at that point. With restart: unless-stopped (as in the file above) Docker simply restarts the container, which now finds app/config/parameters.php, prints "PrestaShop Core already installed" and starts Apache. Without a restart policy, docker start <container> has the same effect. The leftover install.lock is harmless.

  3. A volume on /var/www/html is required. Without one, the installer’s final step, which renames admin to a random name, fails with "The admin folder could not be renamed" because PHP’s rename() returns "Invalid cross-device link" on Docker Desktop’s overlay file system. The Compose file above has the volume.

  4. Setting PS_FOLDER_ADMIN to a non-default value fails: the entrypoint moves the folder, and the installer’s last step then tries to install assets from admin-dev and aborts with "The target directory /var/www/html/admin-dev does not exist". Leave it at the default; the installer already renames the folder to a random name.

  5. Without PS_DOMAIN, the shop is installed for the container’s IP address and the browser on the host is redirected to it. Always set PS_DOMAIN to host:port as seen from the browser.

The first and second items were read from the entrypoint script inside the 9.2.0 image and confirmed on the runs; the volume and PS_FOLDER_ADMIN failures were observed while the test environment for these pages was built (a first attempt without a volume, and one with a custom admin folder name), and the missing PS_DOMAIN behaviour follows the README’s troubleshooting section; they were not repeated afterwards. If the entrypoint loops on "Another setup is currently running", an install.lock was left by an interrupted install; delete it only after checking that no installer runs.

Tags and variants

Use a full version tag such as 9.2.0 in anything you want to keep. The other tags available on 2026-10-27 for 9.2.0 are 9.2.0-apache and 9.2.0-fpm (web server variants), 9.2.0-8.1 to 9.2.0-8.5 (PHP variants of the Apache image) and 9.2.0-6.0-classic with suffixes for the PHP version and -fpm (for example 9.2.0-6.0-classic-8.5-fpm); the 9.2.0-8.5-fpm pattern does not exist. See Versions and Support for the full list and the aliases of latest.

Developing against the container

For module and theme work, mount your source folders into the shop instead of copying them. A bind mount of a module folder is a service volume entry; the module appears in the shop and edits are visible at once, and you then install it from the command line. The container must be created with the mount (add it to the Compose file and run docker compose up -d again), because bind mounts cannot be added to a running container; to add a folder without recreating the container, copy it with docker cp and change its owner.

    volumes:
      - psdata:/var/www/html
      - type: bind
        source: ./modules/pcfaroast
        target: /var/www/html/modules/pcfaroast
      - type: bind
        source: ./themes/pocketcoffee
        target: /var/www/html/themes/pocketcoffee
docker cp ./pcfaroast prestashop-container:/var/www/html/modules/
docker exec prestashop-container chown -R www-data:www-data /var/www/html/modules/pcfaroast
docker exec -u www-data -w /var/www/html prestashop-container php bin/console prestashop:module install pcfaroast
docker exec -u www-data -w /var/www/html prestashop-container php bin/console cache:clear

The docker cp route was used for the Pocket Coffee roast date module on the shared test shop, and the bind-mount route for a demo module; both installed with "Install action on module …​ succeeded". Always run PHP commands as www-data, the owner of the files in the volume, so caches stay writable by the web server. Compose is also the place to add phpMyAdmin (phpmyadmin/phpmyadmin with PMA_HOST set to the database service) and to expose the shop to the Internet for modules that need a public address (ps_accounts, ps_eventbus, ps_metrics) with a tunnel such as ngrok.

PrestaShop Flashlight

PrestaShop Flashlight (the PrestaShop/prestashop-flashlight repository) is a separate Docker project that starts a shop within seconds with demo content and extra tools in the containers (phpMyAdmin, Composer, php-cs-fixer, phpstan, phpunit). Its examples cover installing modules automatically at startup, developing a module, developing PrestaShop itself, an ngrok tunnel and init or post-install scripts; the documentation lists the front office at port 8000, the back office at /admin-dev, and the e-mail admin@prestashop.com with the password prestashop. It is meant for local use only and is documented but was not run for these pages.

The web server

PrestaShop depends on a few rewrite rules (friendly URLs, product image URLs, the /api route to the Webservice), on blocking direct access to source and configuration files, and on request handling that sends everything unknown to index.php. Apache gets most of this from the .htaccess files the shop writes; Nginx has none, so you must reproduce them in the server configuration.

Apache

The shop’s root .htaccess is generated by the installer and by the back office (Shop Parameters > Traffic & SEO > SEO & URLs, in the "Set up URLs" block). From 9.2 you can regenerate it on the command line.

docker exec -u www-data -w /var/www/html prestashop php bin/console prestashop:htaccess:generate
docker exec -u www-data -w /var/www/html prestashop php bin/console prestashop:htaccess:generate --force

The first command printed ".htaccess already exists. Use --force to overwrite." and left the file alone; the second printed ".htaccess successfully generated at /var/www/html/.htaccess". The file records the domain in its rules (#Domain: localhost:8095 and one RewriteCond %{HTTP_HOST} per rule), which matters when you change the domain.

The virtual host must allow .htaccess files to be read and must have mod_rewrite enabled. This is the documented configuration for mod_php or PHP-CGI:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /path/to/prestashop

    <Directory /path/to/prestashop>
        # enable the .htaccess rewrites
        AllowOverride All
        Options -Indexes
        Require all granted
    </Directory>

    ErrorLog /var/log/apache2/prestashop.error.log
    CustomLog /var/log/apache2/prestashop.access.log combined
</VirtualHost>

For PHP-FPM, enable alias, proxy and proxy_fcgi and route .php files with a handler such as SetHandler proxy:unix:/var/run/php/php-fpm.sock|fcgi://dummy; to keep HTTP Basic authentication (used by the Webservice) working with FastCGI, add SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1. The .htaccess that ships with the shop also passes the Authorization header to PHP and blocks composer.lock, .git* and .env*; the modules/.htaccess denies direct access to .php, .log, .txt, .lock, .json, .yml, .yaml, .tpl, .twig, config.xml and config_xx.xml files inside modules. On the Apache image, requests for /modules/ps_emailsubscription/ps_emailsubscription.php and for /composer.lock returned 403. The documentation’s two virtual hosts differ in one line: Options -Indexes for mod_php and Options +Indexes in the PHP-FPM example. Use -Indexes in both so that directory listings are never shown.

Nginx with PHP-FPM

Nginx needs a complete configuration. The sample in the developer documentation is a good start but it is not complete, as the test below shows. This configuration was run with Nginx stable in front of the prestashop/prestashop:9.2.0-fpm image (PHP 8.5.12 FPM on port 9000), sharing the shop volume read-only with the Nginx container; admin504mbtgyopsqszuyouv is the admin folder that installer generated, and you must put your own folder name there.

server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php;
    client_max_body_size 16M;
    error_page 404 /index.php?controller=404;

    # Product and category image URLs
    rewrite ^/(\d)(-[\w-]+)?/.+\.(jpe?g|png|webp|avif)$ /img/p/$1/$1$2.$3 last;
    rewrite ^/(\d)(\d)(-[\w-]+)?/.+\.(jpe?g|png|webp|avif)$ /img/p/$1/$2/$1$2$3.$4 last;
    rewrite ^/c/([\w.-]+)/.+\.(jpe?g|png|webp|avif)$ /img/c/$1.$2 last;

    # Webservice
    rewrite ^/api/?(.*)$ /webservice/dispatcher.php?url=$1 last;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    # Replace with the name of your admin folder
    location /admin504mbtgyopsqszuyouv/ {
        try_files $uri $uri/ /admin504mbtgyopsqszuyouv/index.php$is_args$args;
    }

    location ~ /\.(?!well-known) { deny all; }
    location ~ ^/(app|bin|cache|classes|config|controllers|docs|localization|override|src|tests|tools|translations|var|vendor)/ { deny all; }
    location ~ ^/modules/.*/vendor/ { deny all; }
    location ~ \.(log|tpl|twig|sass|yml)$ { deny all; }

    # Added: the same rules as modules/.htaccess and the root .htaccess
    location ~ ^/modules/.*(\.(php|log|txt|lock|json|yml|yaml|tpl|twig)|config\.xml|config_[a-z]+\.xml)$ { deny all; }
    location ~ ^/(composer\.lock|\.git.*|\.env.*)$ { deny all; }

    location /img { location ~ \.php$ { deny all; } }
    location /upload { location ~ \.php$ { deny all; } }

    location ~ [^/]\.php(/|$) {
        fastcgi_split_path_info ^(.+?\.php)(/.*)$;
        try_files $fastcgi_script_name =404;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        fastcgi_index index.php;
        fastcgi_pass prestashop-fpm:9000;
    }
}

The listing is the documented sample with the image rules shortened to the first rules (the documented sample repeats the product rule for every digit count up to eight digits), a Docker upstream name, no TLS and the two Added rules. The status codes the test shop returned through Nginx:

Request Documented sample alone With the two added rules

/ (home page)

200

200

/api/ without a key

401

401

/app/config/parameters.php

403

403

/var/logs/, /config/defines.inc.php, /.htaccess

403

403

/themes/hummingbird/templates/index.tpl

403

403

/<admin folder>/

302 to the login page

302 to the login page

/modules/ps_emailsubscription/ps_emailsubscription.php

200

403

/modules/ps_emailsubscription/config.xml

200

403

/composer.lock

200

403

/modules/ps_emailsubscription/views/js/ps_emailsubscription.js (a static asset)

200

200

So the sample leaves module PHP files, module config.xml files and composer.lock reachable, where Apache with the shop’s .htaccess files refuses them. Since 9.0 calling a PHP file under modules/ directly is forbidden (a module must use a ModuleFrontController), the added rule is the Nginx equivalent. If the shop runs behind a TLS-terminating proxy or Cloudflare Tunnel, uncomment the sample’s fastcgi_param HTTPS and X-Forwarded-Proto handling so that PHP knows the request was secure; otherwise the back office will not show the "Enable SSL" switch (see below) and redirects can loop. The sample also contains commented security headers (HSTS, X-Frame-Options, X-Content-Type-Options); PrestaShop itself sends none of them and has no Content-Security-Policy support, so add the headers you want at the web server. Multi-language sites with one or two letter language prefixes need the extra rewrite lines kept in the sample.

Installing on your own machine without Docker

An AMP stack (LAMP on Linux, MAMP on macOS, WAMP or XAMPP on Windows) gives you Apache, MySQL and PHP in one package. The steps are the web or command-line installer steps above, plus: create the database and user as shown earlier, put the files under the stack’s document folder (htdocs for XAMPP and MAMP, www for WampServer and EasyPHP), give the web server write access to the listed folders, and open http://127.0.0.1/prestashop/install (install-dev on a development checkout). On macOS and other case-insensitive file systems make the folder name in the Apache configuration match the real one letter for letter: a mismatch such as Prestashop versus PrestaShop produces "Cannot declare class AppKernel, because the name is already in use" the first time you open the back office. These stacks were not run for these pages (Docker was used instead), and the documentation says Windows is not actively supported.

After the installation

The installer leaves a working shop, not a safe one. Work through this list on every new installation; the Security page explains the reasoning.

Step What to do How to check

Admin folder

The installer renames admin to a random name such as admin867rtqhwnkf2hocbvsx and prints the address. Note the new name and keep it out of public pages. If you moved the shop by hand, rename the folder yourself.

ls /var/www/html | grep admin lists one admin folder and no folder called admin or admin-dev.

Install folder

Since 9.2 the installer deletes install/ itself (only a folder named exactly install; install-dev is left alone, and a failure to delete is only logged). On an older version, or a development checkout, delete it by hand.

ls /var/www/html | grep install prints nothing.

HTTPS

Install a certificate and redirect HTTP to HTTPS at the web server. In the back office, Shop Parameters > General has the Enable SSL switch, which stores the configuration key PS_SSL_ENABLED.

The "Enable SSL" switch only appears when you open the back office over HTTPS: the form checks isSecure() on the request. Open it with https:// first.

Production mode

Developer mode and the profiler are for development only. They are switched off by default (_PS_MODE_DEV_ and _PS_DEBUG_PROFILING_ are false in config/defines.inc.php). In Docker leave PS_DEV_MODE at 0 or unset.

php -r 'require "config/config.inc.php"; var_dump(_PS_MODE_DEV_);' prints bool(false). Do not use bin/console for this: its --env option defaults to dev, so bin/console --version prints env: dev, debug: true on a production shop too.

Administrator account

Replace the installer’s temporary password and use a unique e-mail address. In 9.2 the prestashop:employee:change-password and prestashop:employee:create-admin commands manage accounts from the shell.

Log in with the new password; the old one fails.

Web server rules

Apache: AllowOverride All and the generated .htaccess files. Nginx: the complete rules above.

Request /app/config/parameters.php, /composer.lock and a module .php file: all must return 403.

File ownership

The web server user owns the writable folders; nothing is world-writable; app/config/parameters.php holds the database password and the cookie keys and must not be readable by other accounts.

ls -l app/config and find . -perm -002.

Cron, mail and backups

Set up the real mail transport (the installer defaults mailer_transport to smtp on 127.0.0.1), the scheduled tasks that modules need, and regular database and file backups before you load real data.

Send a test e-mail from the back office; restore a backup on a copy.

All of the checks were made on the test installs: the admin folder was renamed and install/ removed by the 9.2.0 installer, and the Apache and Nginx status codes are those shown above. A front-office page request on the Apache test shop returned headers with cookies marked HttpOnly and SameSite=Lax and no Content-Security-Policy, Strict-Transport-Security or X-Frame-Options header. The install does not set the database user’s privileges for you: give the shop’s database user rights on its own database only, not on the whole server. The default table prefix ps_ and database name prestashop are well known; the installer lets you change them, which costs nothing and slightly raises the effort of a blind SQL injection attempt.

Moving a shop to another server or changing its domain

A PrestaShop shop is the files, the database and a handful of places where the address is written. Moving to a new server copies the first two and fixes the third. Changing the domain on the same server only fixes the third. The procedure below was run on a throwaway shop (the command-line install above) by moving its domain from localhost:8095 to shop.example.test:8095, which is the domain-change half of a move; copying files and a database is ordinary system administration.

  1. Back up, and test the backup. Dump the database and archive the files, and keep both until the new shop works.

  2. Copy the files and the database to the new server. Create the database user there. Keep app/config/parameters.php consistent with the new server: update database_host, database_name, database_user, database_password and database_prefix if they changed (and leave secret and cookie_key as they are, or all sessions and encrypted values become invalid).

  3. Change the stored domain in the database: the row of the shop in the shop_url table (columns domain, domain_ssl and physical_uri), and the two configuration keys PS_SHOP_DOMAIN and PS_SHOP_DOMAIN_SSL. A multistore has one shop_url row per shop and domain.

  4. Regenerate .htaccess, which has the old domain written in its rewrite conditions.

  5. Clear the cache, then test.

  6. Fix the addresses written inside content: CMS pages, product and category descriptions, e-mail templates edited by hand and anything an editor pasted with an absolute link. Search a dump for the old host.

  7. Point DNS and certificates at the new server last.

# 1. backup
docker exec db mysqldump -uroot -p"${DB_ROOT_PASSWORD}" --no-tablespaces pocketcoffee > pocketcoffee.sql

# 3. the domain, in the database
docker exec db mysql -uroot -p"${DB_ROOT_PASSWORD}" pocketcoffee -e "
  UPDATE pcf_shop_url
     SET domain = 'shop.example.test:8095', domain_ssl = 'shop.example.test:8095'
   WHERE id_shop_url = 1;
  UPDATE pcf_configuration
     SET value = 'shop.example.test:8095'
   WHERE name IN ('PS_SHOP_DOMAIN', 'PS_SHOP_DOMAIN_SSL');"

# 4. and 5. rules and cache
docker exec -u www-data -w /var/www/html prestashop php bin/console prestashop:htaccess:generate --force
docker exec -u www-data -w /var/www/html prestashop php -d memory_limit=1G bin/console cache:clear --env=prod

The SQL uses the table prefix pcf_ of the command-line install example above, and the container names prestashop and db stand for yours; the throwaway test shop used other names and the prefix pcfa_. What happened on the test shop:

  • After the database change, before .htaccess was regenerated, the new host answered 200 with curl --resolve shop.example.test:8095:127.0.0.1 and a request to the old localhost:8095 was redirected with 302 to http://shop.example.test:8095/: PrestaShop redirects any request whose host is not the main domain of the shop to that domain, so a wrong stored domain also breaks health checks that use the address.

  • prestashop:htaccess:generate --force rewrote .htaccess (ten occurrences of the new domain afterwards).

  • cache:clear --env=prod crashed the first time with "Allowed memory size of 536870912 bytes exhausted" while warming the production cache, because the CLI memory_limit of the image is 512M. Running it again with php -d memory_limit=1G succeeded: "Cache for the prod environment (debug=false) was successfully cleared." The next front request took about a second while the cache rebuilt.

  • Content searching: with the dump of the database and grep the old domain was found in four rows, three in the statistics table connections_source (visits recorded by the earlier requests, harmless) and one in a CMS page where a test link had been added. Replacing it in the page text worked:

UPDATE pcf_cms_lang
   SET content = REPLACE(content, 'http://localhost:8095/', 'http://shop.example.test:8095/')
 WHERE content LIKE '%localhost:8095%';

The statement changed one row. Apply the same idea to the description and content columns of the product, category, CMS and manufacturer language tables; never run it blindly on serialized data, because changing the length of a PHP-serialized string corrupts it (look for columns that start with a: or O: first, or use a tool that understands serialization).

If the address of the shop changes on every start, as it does for a container whose IP or port is not fixed, set PS_HANDLE_DYNAMIC_DOMAIN=1 on the Docker image so that the stored domain follows the request; do not use it in production. For a move to a new major version rather than a new server, see the migration process described in Versions and Support. A shop moved to a different folder on the same host also needs physical_uri and --base_uri consistent with the new path.

Troubleshooting

Symptom Cause What to do

The browser is redirected from localhost:8080 to a container IP and fails

The shop was installed with the container’s IP as its domain because PS_DOMAIN was not set

Set PS_DOMAIN=localhost:8080 and reinstall, or fix the stored domain as shown above

Install ends with "The admin folder could not be renamed" or "Invalid cross-device link"

The shop folder is not on a volume

Mount a named volume on /var/www/html

Install aborts with "The target directory /var/www/html/admin-dev does not exist"

A non-default PS_FOLDER_ADMIN was set

Remove the variable

Container exits with status 1 right after a successful install

The entrypoint’s rm -r install/ fails because the installer already removed the folder

Restart the container; use restart: unless-stopped

"Your server is running PHP x, but PrestaShop requires a PHP version between PHP 8.1 and PHP 8.5"

PHP is older than 8.1 or newer than 8.5

Use a PHP version in the supported range

/api returns 404 or the Webservice cannot be enabled

URL rewriting is not active

Enable mod_rewrite and AllowOverride All, or add the Nginx /api rewrite

"Cannot declare class AppKernel, because the name is already in use"

The Apache path differs in letter case from the real folder name on a case-insensitive file system

Make the capitalisation identical

The installer fails with "Server sent charset (255) unknown to the client"

An old database client or driver talks to MySQL 8 with utf8mb4

Use a current PHP image, or change the server charset as the Docker README describes

cache:clear --env=prod is killed with "Allowed memory size exhausted"

CLI memory_limit of 512M is not enough to warm the production cache

Run php -d memory_limit=1G bin/console cache:clear --env=prod

Admin API answers "Use HTTPS protocol"

Plain HTTP in production mode

Use HTTPS; the exception for local development is described on the Admin API page

What could not be run

Everything shown above as run was run on 2026-10-27 with the official Docker images, a MySQL 8.4.13 server and Nginx stable. The following were documented from the official sources and not executed: the browser-driven web installer (only its first page and step list were read), the release zip from prestashop.com (the image contains the same payload), the installation on a host AMP stack (XAMPP, MAMP, WampServer) or with Apache and PHP-FPM, PrestaShop Flashlight, a TLS certificate and the HTTPS switch of the back office, PS_HANDLE_DYNAMIC_DOMAIN, and a real move between two servers (only the change of domain on one server was run).