Magento installation services powered by Nginx

Alex KondratievAlex Kondratiev

11 min read

A tested, step-by-step Magento Open Source 2.4.9 installation on Ubuntu with Nginx, PHP-FPM 8.5, MySQL 8.4, OpenSearch 3 and Valkey. You end up with a store in production mode over HTTPS, with cron and admin two-factor authentication working. Tested on Ubuntu 26.04 and 24.04.


This guide installs Magento Open Source 2.4.9, the current release, on a single Ubuntu server. Every command below was run from a clean server to a working storefront and admin panel. Here's the stack, built on the versions Adobe supports for 2.4.9:

  • Ubuntu 26.04 LTS (differences for 24.04 are in a separate section)
  • Nginx as the web server, with Magento's own config
  • PHP-FPM 8.5
  • MySQL 8.4 LTS
  • OpenSearch 3 for catalog search
  • Valkey 9 for cache and sessions
  • Composer 2 and Certbot for HTTPS

⚠️ Magento 1 reached end of life in June 2020 and gets no security fixes. If an old tutorial tells you to download a magento-1.9.x.tar.gz, skip it. Everything here is Magento 2.

What you need

  • A server with Ubuntu 26.04 LTS and a user with sudo. Plan for at least 4 GB of RAM: in our test the stack used about 2.5 GB right after install, most of it OpenSearch and MySQL. For a real store with traffic, start at 8 GB.
  • A domain name with an A record pointing to the server. This guide uses shop.example.com, replace it everywhere with yours.
  • Ports 80 and 443 open to the internet.
  • Magento Marketplace access keys. They're free, and you'll need them in Step 8.

Install the server packages

Step 1: Update the server

Start with a fully updated system:

shell

1sudo apt update && sudo apt upgrade -y

If the upgrade pulled in a new kernel, reboot before you continue.

Step 2: Install PHP 8.5 and the extensions Magento needs

Ubuntu 26.04 ships PHP 8.5, which is the version Adobe tests 2.4.9 with. This one command installs PHP-FPM, the CLI and every extension Magento checks for during installation:

shell

1sudo apt install -y php8.5-fpm php8.5-cli php8.5-mysql php8.5-bcmath php8.5-curl php8.5-gd php8.5-intl php8.5-mbstring php8.5-soap php8.5-xml php8.5-zip unzip cron

OPcache is built into PHP 8.5, so there's no separate package for it. cron is on most server images already, but minimal images don't have it, and Magento can't run without it.

Step 3: Install MySQL 8.4 and create the database

Install the MySQL server from Ubuntu's repositories. On 26.04 that's MySQL 8.4 LTS:

shell

1sudo apt install -y mysql-server

MySQL 8.4 has binary logging turned on by default. With binary logging on, MySQL refuses to let a regular user create the triggers Magento needs, and the installation fails halfway with error 1419. Allow it before you create anything:

shell

1cat <<'EOF' | sudo tee /etc/mysql/mysql.conf.d/magento.cnf
2[mysqld]
3log_bin_trust_function_creators = 1
4EOF
5sudo systemctl restart mysql

Now create the database and a user for Magento. On Ubuntu, the MySQL root account logs in through the system's root user, so sudo mysql works without a password. Pick a strong password for the magento user:

shell

1sudo mysql <<'SQL'
2CREATE DATABASE magento;
3CREATE USER 'magento'@'localhost' IDENTIFIED BY 'Ch4nge-This-Passw0rd';
4GRANT ALL PRIVILEGES ON magento.* TO 'magento'@'localhost';
5SQL

Older guides create the user with GRANT ... IDENTIFIED BY in one line. MySQL 8 removed that syntax, so CREATE USER comes first and GRANT second.

Step 4: Install OpenSearch 3

Magento 2.4 needs a search engine, and OpenSearch is the one Adobe recommends. It isn't in Ubuntu's repositories, so add the official OpenSearch repository:

shell

1sudo install -d -m 0755 /etc/apt/keyrings
2curl -fsSL https://artifacts.opensearch.org/publickeys/opensearch-release.pgp | sudo gpg --dearmor -o /etc/apt/keyrings/opensearch.gpg
3echo "deb [signed-by=/etc/apt/keyrings/opensearch.gpg] https://artifacts.opensearch.org/releases/bundle/opensearch/3.x/apt stable main" | sudo tee /etc/apt/sources.list.d/opensearch-3.x.list
4sudo apt update

Install it with the security plugin turned off:

shell

1sudo env DISABLE_INSTALL_DEMO_CONFIG=true DISABLE_SECURITY_PLUGIN=true apt install -y opensearch

Turning security off is fine here because OpenSearch will only listen on 127.0.0.1, and nothing outside the server can reach it. If you ever move OpenSearch to a separate server, keep the security plugin on and use the --opensearch-enable-auth options of the Magento installer.

Bind OpenSearch to localhost and run it as a single node, then start it:

shell

1cat <<'EOF' | sudo tee -a /etc/opensearch/opensearch.yml
2network.host: 127.0.0.1
3discovery.type: single-node
4EOF
5sudo systemctl daemon-reload
6sudo systemctl enable --now opensearch

The package sets a 1 GB Java heap by default, which is enough for a small catalog. Give it a few seconds to start, then check it:

shell

1curl -s http://127.0.0.1:9200

You should get a JSON answer with "number" : "3.9.0" or newer.

Step 5: Install Valkey and Nginx

Valkey is the open-source fork of Redis, and since 2.4.9 it's what Magento's tooling expects for cache and sessions. Ubuntu 26.04 has Valkey 9 in its repositories:

shell

1sudo apt install -y valkey-server nginx

Both start right away. Valkey listens on 127.0.0.1 only out of the box, so there's nothing to lock down. Check it:

shell

1valkey-cli ping

The answer is PONG.

Step 6: Install Composer

Magento is installed and updated with Composer. Install the current Composer 2 with the official installer, which checks its own signature before it runs:

shell

1cd /tmp
2curl -sS https://getcomposer.org/installer -o composer-setup.php
3EXPECTED=$(curl -sS https://composer.github.io/installer.sig)
4php -r "if (hash_file('sha384', 'composer-setup.php') !== '$EXPECTED') { echo 'Installer corrupt'.PHP_EOL; exit(1); } echo 'Installer verified'.PHP_EOL;"
5sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer
6rm composer-setup.php
7composer --version

Download Magento

Step 7: Create a system user for Magento

Don't run Magento as root or as www-data. Create a dedicated user that owns the code and runs PHP. It gets no password and no login shell:

shell

1sudo useradd --create-home --shell /usr/sbin/nologin magento
2sudo usermod -aG magento www-data
3sudo install -d -o magento -g magento -m 750 /var/www/shop

Here's how the permissions work:

  • The magento user owns everything in /var/www/shop. PHP-FPM runs as this user too, so Magento can write its cache, generated code and media without any chmod 777.
  • Nginx runs as www-data. Adding it to the magento group lets it read static files, but not change them.
  • The 750 mode keeps every other user on the server out of the directory, including out of app/etc/env.php with your database password.

From now on, run every bin/magento and composer command as this user with sudo -u magento. If you run them as root, you'll end up with root-owned files that PHP can't write to.

One side effect of the 750 mode: your own user can't cd into /var/www/shop anymore. That's why all commands below use full paths.

Step 8: Get your access keys and download Magento 2.4.9

Magento's packages live on repo.magento.com, and Composer needs access keys to download them. They're free:

  1. Sign in or register on the Adobe Commerce Marketplace.
  2. Click your account name in the top-right corner and select My Profile.
  3. On the Marketplace tab, click Access Keys.
  4. Click Create a New Access Key, give it a name and click OK.

Adobe has the full walkthrough with screenshots. Save the keys in the magento user's Composer config. The public key goes in as the username and the private key as the password. Run this command:

shell

1sudo -u magento composer config --global http-basic.repo.magento.com <public-key> <private-key>

Now download Magento into the empty directory:

shell

1sudo -u magento composer create-project --repository-url=https://repo.magento.com/ magento/project-community-edition=2.4.9 /var/www/shop

Composer pulls a few hundred packages, so this takes a few minutes. When Adobe ships a security patch for 2.4.9, it's published as a version like 2.4.9-p1. Check the release notes and use the newest one.

Configure PHP-FPM and Nginx

Step 9: Create a PHP-FPM pool for Magento

Give Magento its own PHP-FPM pool that runs as the magento user, with the memory limit Adobe recommends:

shell

1cat <<'EOF' | sudo tee /etc/php/8.5/fpm/pool.d/magento.conf
2[magento]
3user = magento
4group = magento
5listen = /run/php/php8.5-fpm-magento.sock
6listen.owner = www-data
7listen.group = www-data
8listen.mode = 0660
9
10pm = dynamic
11pm.max_children = 20
12pm.start_servers = 4
13pm.min_spare_servers = 2
14pm.max_spare_servers = 6
15pm.max_requests = 500
16
17php_admin_value[memory_limit] = 2G
18php_admin_flag[display_errors] = off
19php_admin_flag[log_errors] = on
20php_admin_value[realpath_cache_size] = 10M
21php_admin_value[realpath_cache_ttl] = 7200
22EOF

display_errors stays off, so visitors never see PHP errors with file paths. Errors go to the PHP-FPM log instead.

Magento has thousands of PHP files, so OPcache needs more room than the defaults give it:

shell

1cat <<'EOF' | sudo tee /etc/php/8.5/mods-available/magento.ini
2opcache.memory_consumption = 512
3opcache.interned_strings_buffer = 32
4opcache.max_accelerated_files = 60000
5EOF
6sudo phpenmod -v 8.5 -s fpm magento
7sudo php-fpm8.5 -t
8sudo systemctl restart php8.5-fpm

php-fpm8.5 -t should end with test is successful. Tune pm.max_children to your RAM later: each PHP process for Magento takes roughly 100 to 150 MB under load.

Step 10: Create the Nginx server block

Magento comes with a ready Nginx config, nginx.conf.sample. It sets the document root to pub/, blocks access to sensitive files and handles static files and the PHP entry points. Copy it, so a future Magento update doesn't change your live config without you noticing:

shell

1sudo -u magento cp /var/www/shop/nginx.conf.sample /var/www/shop/nginx.conf

Your server block only has to point it at the PHP-FPM socket and the Magento directory:

nginx

1upstream fastcgi_backend {
2 server unix:/run/php/php8.5-fpm-magento.sock;
3}
4
5server {
6 listen 80;
7 listen [::]:80;
8 server_name shop.example.com;
9
10 set $MAGE_ROOT /var/www/shop;
11 include /var/www/shop/nginx.conf;
12}

Save it as /etc/nginx/sites-available/shop.example.com, then enable it and turn off the default site:

shell

1sudo ln -s /etc/nginx/sites-available/shop.example.com /etc/nginx/sites-enabled/
2sudo rm /etc/nginx/sites-enabled/default
3sudo nginx -t
4sudo systemctl reload nginx

At this point the site redirects to /setup/, and that's expected: Magento isn't installed yet.

Turn on HTTPS

Step 11: Get a Let's Encrypt certificate

Get the certificate before you install Magento, so the store uses HTTPS from the first request. Install Certbot and run it with the Nginx plugin:

shell

1sudo snap install --classic certbot
2sudo ln -s /snap/bin/certbot /usr/local/bin/certbot
3sudo certbot --nginx -d shop.example.com

Certbot adds the listen 443 ssl lines and certificate paths to your server block and sets up a redirect from HTTP to HTTPS. It also sets up automatic renewal. Our Let's Encrypt guide for Nginx covers the options, renewal checks and errors in detail.

Just testing on a server without a domain? Skip this step and use an http:// base URL in the next one.

Install Magento

Step 12: Run the installer

bin/magento setup:install creates the database tables, the admin account and the config file. Every option fits in one command:

shell

1sudo -u magento /var/www/shop/bin/magento setup:install \
2 --base-url=https://shop.example.com/ \
3 --base-url-secure=https://shop.example.com/ \
4 --use-secure=1 \
5 --use-secure-admin=1 \
6 --db-host=localhost \
7 --db-name=magento \
8 --db-user=magento \
9 --db-password 'Ch4nge-This-Passw0rd' \
10 --admin-firstname=Store \
11 --admin-lastname=Admin \
12 --admin-email=you@example.com \
13 --admin-user=storeadmin \
14 --admin-password 'An0ther-Str0ng-Passw0rd' \
15 --language=en_US \
16 --currency=USD \
17 --timezone=UTC \
18 --use-rewrites=1 \
19 --search-engine=opensearch \
20 --opensearch-host=127.0.0.1 \
21 --opensearch-port=9200 \
22 --cache-backend=valkey \
23 --cache-backend-valkey-server=127.0.0.1 \
24 --cache-backend-valkey-db=0 \
25 --page-cache=valkey \
26 --page-cache-valkey-server=127.0.0.1 \
27 --page-cache-valkey-db=1 \
28 --session-save=valkey \
29 --session-save-valkey-host=127.0.0.1 \
30 --session-save-valkey-db=2

A few notes on the options:

  • The admin email must be real. Magento sends the two-factor authentication setup and password resets there.
  • The admin password needs at least 7 characters with both letters and numbers.
  • Cache, full page cache and sessions go to three separate Valkey databases (0, 1 and 2), so clearing the cache never logs anyone out.
  • There's no --backend-frontname, so Magento picks a random admin URL. That's one less thing for bots to guess.

The installer ends like this:

shell

1[SUCCESS]: Magento installation complete.
2[SUCCESS]: Magento Admin URI: /admin_cmvppn1

Write down your admin URI. It'll be different from this one.

Step 13: Switch to production mode

A fresh install runs in default mode, which builds static files on the fly and is slow. Production mode compiles the code and deploys static files once:

shell

1sudo -u magento /var/www/shop/bin/magento deploy:mode:set production

It ends with Enabled production mode. Run the same command again after you install a module or a theme, because production mode doesn't pick up code changes on its own.

Step 14: Set up cron

Magento depends on cron for indexing, emails, sitemaps and cleanup. Let Magento add its own entry to the magento user's crontab:

shell

1sudo -u magento /var/www/shop/bin/magento cron:install
2sudo crontab -u magento -l

You should see one line that runs bin/magento cron:run every minute:

shell

1#~ MAGENTO START 1512c70a3359c2b49c9c362581c8aa331316a139d601e99a9a18c78d62867d0d
2* * * * * /usr/bin/php8.5 /var/www/shop/bin/magento cron:run >> /var/www/shop/var/log/magento.cron.log 2>&1
3#~ MAGENTO END 1512c70a3359c2b49c9c362581c8aa331316a139d601e99a9a18c78d62867d0d

Log in to the admin panel

Step 15: Set up two-factor authentication

Open https://shop.example.com/admin_xxxxxxx/ with your admin URI and log in. Magento 2.4 requires two-factor authentication for every admin user, so the first login stops at this message:

shell

1You need to configure Two-Factor Authorization in order to proceed to your store's admin area

The page also says an email was sent to you with further instructions. On a fresh server, outgoing mail often doesn't work yet, so that email never arrives. In that case, set up Google Authenticator from the command line instead:

shell

1sudo -u magento /var/www/shop/bin/magento config:set twofactorauth/general/force_providers google
2SECRET=$(head -c 20 /dev/urandom | base32 | tr -d '=')
3sudo -u magento /var/www/shop/bin/magento security:tfa:google:set-secret storeadmin "$SECRET"
4sudo -u magento /var/www/shop/bin/magento cache:flush
5echo "$SECRET"

Add the printed secret to Google Authenticator, Authy or any TOTP app as a setup key. Log in again, and Magento asks for the 6-digit code from the app instead of the email. After that you land on the dashboard.

Check that everything works

Run a few checks from the server itself:

shell

1curl -s -o /dev/null -w "%{http_code}\n" http://shop.example.com/
2curl -s -o /dev/null -w "%{http_code}\n" https://shop.example.com/
3curl -s -o /dev/null -w "%{http_code}\n" "https://shop.example.com/catalogsearch/result/?q=bag"
4sudo mysql -e "SELECT status, COUNT(*) FROM magento.cron_schedule GROUP BY status;"

Here's what a working install returns:

  • 301 for HTTP, because Certbot redirects it to HTTPS.
  • 200 for the home page and for search.
  • success rows in cron_schedule a couple of minutes after Step 14.

The first page load after a deploy takes a moment. The next ones come from the full page cache in Valkey, and in our test they took about 20 ms on the server.

Reboot the server once to make sure everything comes back. Nginx, PHP-FPM, MySQL, OpenSearch, Valkey and cron are all enabled as services, so the store should be up without you touching anything.

On Ubuntu 24.04

The steps are the same, except that Ubuntu 24.04 ships older versions of PHP, MySQL and Valkey than Magento 2.4.9 is tested with. Here's what changes:

  • PHP 8.5. Ubuntu 24.04 has PHP 8.3. Get PHP 8.5 from the widely used PPA of Ondřej Surý, the Debian PHP maintainer, then install the same packages as in Step 2:

shell

1sudo add-apt-repository -y ppa:ondrej/php
2sudo apt install -y php8.5-fpm php8.5-cli php8.5-mysql php8.5-bcmath php8.5-curl php8.5-gd php8.5-intl php8.5-mbstring php8.5-soap php8.5-xml php8.5-zip unzip cron
  • MySQL 8.4. Ubuntu 24.04 has MySQL 8.0, which reached end of support in April 2026. Add Oracle's MySQL 8.4 LTS repository before Step 3. It has packages for amd64 servers only:

shell

1sudo install -d -m 0755 /etc/apt/keyrings
2curl -fsSL https://repo.mysql.com/RPM-GPG-KEY-mysql-2025 | sudo gpg --dearmor -o /etc/apt/keyrings/mysql.gpg
3echo "deb [signed-by=/etc/apt/keyrings/mysql.gpg] http://repo.mysql.com/apt/ubuntu noble mysql-8.4-lts" | sudo tee /etc/apt/sources.list.d/mysql.list
4sudo apt update
5sudo apt install -y mysql-server
  • Valkey. Ubuntu 24.04 has Valkey 7.2, and there's no official repository with a newer version. Magento 2.4.9 works with it in our test, but Adobe only lists Valkey 9 as supported. For an exact match, build Valkey 9 from source or move to Ubuntu 26.04.

Everything else, from OpenSearch to cron, works on 24.04 exactly as written above.

Troubleshooting

SQLSTATE[HY000]: General error: 1419 You do not have the SUPER privilege and binary logging is enabled. The installer stopped while creating triggers. Add log_bin_trust_function_creators = 1 as shown in Step 3 and restart MySQL. Then drop and recreate the magento database, delete app/etc/env.php and run setup:install again.

502 Bad Gateway with connect() to unix:/run/php/... failed (2: No such file or directory) in the Nginx error log. The socket in the upstream fastcgi_backend block doesn't match the listen line of the PHP-FPM pool. Make them the same, check that php8.5-fpm is running, and reload Nginx.

Search returns nothing and var/log/exception.log says No alive nodes found in your cluster. Magento can't reach OpenSearch. Check sudo systemctl status opensearch and curl -s http://127.0.0.1:9200. The search page still answers 200 in this case, so the log is the only place you'll see the error.

The admin login loops back to the 2FA message. No 2FA provider is configured yet. Run the commands from Step 15.

Pages load without styles after you installed a module. Production mode needs a rebuild after every code change. Run sudo -u magento /var/www/shop/bin/magento deploy:mode:set production again.

Keep it running

A Magento store is never "done". A few things to plan for:

  • Updates. Adobe releases security patches for 2.4.9 regularly. Update with Composer as the magento user, then run bin/magento setup:upgrade and rebuild production mode.
  • Backups. Back up the database with mysqldump and the pub/media and app/etc directories every day, and test a restore.
  • Varnish. The full page cache in Valkey is plenty for a small store. Under heavy traffic, Adobe recommends Varnish 8 in front of Nginx.

Need help with Magento?

Installing Magento is the easy part. Keeping it fast, patched and online through a sale is where it gets hard.

ITSyndicate's engineers can set up your Magento infrastructure, automate deployments and look after it day to day, from a single server like this one to a scaled cloud setup. See our cloud engineering services or contact us to talk about your store.

Alex Kondratiev

Alex Kondratiev

Founder of ITsyndicate. DevOps Enthusiast with 15+ years of experience in cloud, Infrastructure as Code, Kubernetes, and automation. Specialized in architecting secure, scalable, and resilient systems.

Stay online.
Stay in command.