How to Install WordPress on Debian 13: Commands and Steps

20 min read

When I ran the commands of an Ubuntu install on a new Debian 13 server, the install failed twice because of the PHP version: Debian 13 comes with PHP 8.4, and Ubuntu 26.04 with PHP 8.5. First, apt install php8.5-fpm ended with "Unable to locate package". Then the socket path I'd copied from the Ubuntu server block gave every page a 502 Bad Gateway error, although nginx -t said the configuration was fine. A third failure had nothing to do with PHP: after I'd switched to root, nginx -t gave me "command not found".

If you want to install WordPress on Debian 13 (codename trixie) with nginx, PHP-FPM and MariaDB, most of the work is the same as on Ubuntu, and my Ubuntu guide has those steps in full. What's different is a short list: root access without sudo, the PHP packages and the sury.org repository, the socket path in nginx, the firewall, automatic security updates and Certbot.

I tested every command in a new Debian 13.7 container and finished with WordPress and WooCommerce running. Here's the short version: write 8.4 wherever an Ubuntu guide writes 8.5, become root with su - (with the dash) when there's no sudo, set up the firewall yourself, and check whether automatic security updates are on, because on Debian that depends on how the server was installed.

How installing WordPress on Debian 13 differs from Ubuntu 26.04

I compared the two systems on the same day in two ways: I ran apt-cache policy on a new container of each, and I read the package lists of the official cloud images both projects publish. A cloud image is the ready-made system a new cloud server is copied from, and Debian's is small: it has fewer than half the packages of Ubuntu's.

On a new serverDebian 13.7Ubuntu 26.04.1
PHP from the system's own packages8.4.27, in main8.5.4, php8.5-fpm in universe
PHP-FPM service and socketphp8.4-fpm, /run/php/php8.4-fpm.sockphp8.5-fpm, /run/php/php8.5-fpm.sock
nginx1.26.31.28.3
MariaDB11.8.611.8.6
Certbot from apt4.0.04.0.0
sudoin the cloud image; from the installer only when root has no passwordalways
add-apt-repositorynot in Debian 13installed
snapnot installedinstalled
Firewallno ufw, no active rulesufw installed, off until enabled
Automatic security updateson in the cloud image, off after the installeron
/usr/sbin in a normal user's PATHnoyes
Packages in the cloud image322678
nginx layout, web user, web rootsites-available, www-data, /var/www/htmlthe same

I took the versions from apt on the day I tested. The sudo, snap, firewall and update rows come from the official cloud images, the Debian installer and Ubuntu's server documentation.

Two rows are the same on both systems, so you have less to change: MariaDB is the same release on both, and the nginx layout row is the same too (the sites folders, the www-data user and the web root). So the database and WordPress steps of the Ubuntu guide work as written, except the sudo -u www-data in front of each WP-CLI command when your server has no sudo (the section on root access shows what to type instead). Ubuntu commands fail where the PHP version differs, and some fail even earlier, because sudo is missing.

Root access when Debian has no sudo

Ubuntu guides start every command with sudo, because Ubuntu always has it. On Debian, whether sudo is there depends on how the server was installed. The official Debian cloud image includes sudo. The Debian installer adds sudo only when you leave the root password empty, and then the installer gives the first user sudo rights (the installation guide says so in its part on the root password).

If you set a root password during the install, the first Ubuntu command fails at once:

root@server:~# sudo apt update
-bash: sudo: command not found

Without sudo you'll log in as your own user and switch to root with su, and then commands such as nginx -t, typed the way you'd type them on Ubuntu, fail. Plain su keeps your own PATH, and a normal user's PATH on Debian doesn't include /usr/sbin, the folder that holds nginx, PHP-FPM and most admin tools. Ubuntu adds /usr/sbin and the other admin folders to every user's PATH in /etc/environment. On my Debian test server that file was empty.

Here's what happened when I logged in as a user named shop:

shop@server:~$ su
Password:
root@server:/home/shop# nginx -t
bash: nginx: command not found
root@server:/home/shop# exit
exit
shop@server:~$ su -
Password:
root@server:~# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

The dash starts a login shell with root's own PATH. Plain su has kept your own PATH since Debian 10, when su moved to the util-linux package, and the Debian wiki's page on that release warns that after a plain su "many system administration commands will fail". Would you rather type sudo, the way you do on Ubuntu? Then install sudo once as root and add your user to the sudo group (the new group works from your next login):

root@server:~# apt install sudo
root@server:~# usermod -aG sudo shop

WP-CLI needs a different command too. Ubuntu guides run it as the web server user with sudo -u www-data wp ..., and su can't do that, because www-data doesn't have a login shell. When you're root, runuser does what sudo -u does, and since it comes with util-linux, every Debian server has it:

root@server:/var/www/example.com# su www-data -c "wp core version"
This account is currently not available.
root@server:/var/www/example.com# runuser -u www-data -- wp core version
7.1.3

Install PHP 8.4 from Debian's own packages

Debian 13 has one PHP version in its archive (Debian's own package repository): 8.4. The package names contain the version (php8.4-fpm and so on), and generic names such as php-fpm install those same packages. An apt install line with php8.5 package names, copied from a current Ubuntu guide, stops before it installs anything, and the php8.3 names from older guides stop the same way. The ondrej/php PPA (a personal package archive on Ubuntu's Launchpad) that many guides add won't help either, because Debian doesn't have add-apt-repository:

root@server:~# apt install -y php8.5-fpm
...
Error: Unable to locate package php8.5-fpm
Error: Couldn't find any package by glob 'php8.5-fpm'
root@server:~# add-apt-repository ppa:ondrej/php
-bash: add-apt-repository: command not found

On Debian I type the generic names, because they always install the PHP version the release supports. This is the whole stack I installed, PHP extensions included:

root@server:~# apt update
root@server:~# apt install nginx mariadb-server php-fpm php-mysql php-curl php-gd php-intl \
    php-mbstring php-xml php-zip php-imagick php-bcmath php-redis php-soap redis-server curl unzip

On a new Debian 13 server that line installs 253 packages, and it took between one and two minutes in my two runs. On a real server nginx, MariaDB, Redis and PHP-FPM start during that install, and they're already enabled for the next boot. Redis is in Debian's main archive too, so the reason the Ubuntu guide gives for switching to Valkey (redis-server is in universe there) doesn't apply here. Debian 13 has valkey-server as well, if you'd rather run it.

PHP's settings are then in /etc/php/8.4/fpm/ (php.ini, and the pool file pool.d/www.conf), and nginx reaches PHP through /run/php/php8.4-fpm.sock. Wherever an Ubuntu guide says 8.5, you write 8.4: in commands and in paths, including the path of the php.ini you edit for upload limits (/etc/php/8.4/fpm/php.ini) (the 413 and upload_max_filesize guide has those settings).

PHP 8.4 is the newest version WooCommerce lists as tested

Does a store lose anything with the older PHP? WooCommerce's own server requirements say it doesn't. They recommend PHP 8.3 or greater and name 8.4 as the newest version they've tested, so Debian's PHP is the newest one WooCommerce lists as tested, and Ubuntu's 8.5 is one version newer than any version on that list. WordPress asks for 8.3 or greater too.

The support period isn't a problem either. php.net keeps fixing security bugs in PHP 8.4 until the end of 2028, and that's a few months after Debian's regular support for this release has already ended.

For me, the bigger difference is who delivers the security fixes. On Debian, php8.4-fpm is in the main archive and the Debian security team patches it: the build I installed came from trixie-security. On Ubuntu, php8.5-fpm (the package that actually runs WordPress behind nginx) is in universe. Canonical's standard five years of security updates cover main, and universe is outside main. That's why the Ubuntu guide recommends Ubuntu Pro for a store on 26.04. On Debian you don't need that step.

Don't install Debian's wordpress package. Debian's build is WordPress 6.8.10, while wordpress.org is at 7.1.3, and on a new container apt wanted 149 packages for it, Apache and its PHP module among them. You don't need any of that: download WordPress with WP-CLI, the way the Ubuntu guide does.

How to install PHP 8.5 from sury.org on Debian 13

Do you need another PHP at all? Only if a plugin needs PHP 8.5, or an old one still needs 8.3. Then the repository for Debian is packages.sury.org, run by Ondrej Sury, a Debian developer who has packaged PHP for Debian for many years. It's the Debian counterpart of the ondrej/php PPA that Ubuntu users add: the same developer and the same PHP versions, built for Debian (a PPA is built for Ubuntu releases only).

Debian's extrepo tool already has the repository and its signing key in its list, so three commands add it:

root@server:~# apt install extrepo
root@server:~# extrepo enable sury
root@server:~# apt update

The README on packages.sury.org shows the same setup done by hand, with a keyring package and a php.list file. Use extrepo or the README setup, never both, because when I had both, apt wouldn't read its sources at all ("Conflicting values set for option Signed-By"). For Debian 13 the repository had every PHP from 8.1 to 8.5.

The next apt upgrade shows the downside. With Debian's PHP already installed, adding sury.org made 29 packages upgradable: 28 PHP packages with the same PHP version, rebuilt by sury.org with a higher package number, and libgd3, the image library behind PHP's GD extension. Run that upgrade and your PHP comes from one developer's repository instead of Debian's, signed with a key that expires in February 2028. His own page says he does this work mostly in his free time.

Automatic updates skip sury.org packages: unattended-upgrades installs only packages from the origins (apt's name for who published a package) on its allowed list, Debian and Debian-Security. In another test container with fewer PHP extensions, 24 sury.org updates were waiting, and a dry run of unattended-upgrades (it lists what it would install and installs nothing) answered "No packages found that can be upgraded unattended". Your automatic updates skip every sury.org package until you add sury.org to that allowed list, which takes one line in a new file:

root@server:~# cat /etc/apt/apt.conf.d/51unattended-upgrades-sury
Unattended-Upgrade::Origins-Pattern:: "origin=deb.sury.org,codename=${distro_codename}";

With that line in place, the dry run listed php8.4-fpm and the other sury.org packages.

Another problem appeared when I installed the newer PHP next to Debian's. php -v switched to 8.5 by itself, because Debian's alternatives system points the php command at the highest version installed, while the store kept running 8.4 through its socket. WP-CLI and cron jobs used PHP 8.5, while the store's pages still used PHP 8.4. update-alternatives --set php /usr/bin/php8.4 sets the php command back to the site's version.

For a WooCommerce store I'd stay on Debian's 8.4 until a plugin I need requires a different PHP version. It's the version WooCommerce tests, and its fixes arrive in the same apt upgrade as the rest of the system.

nginx 1.26 uses the same folders as Ubuntu

Debian's nginx is one stable release older than Ubuntu's (1.26 against 1.28), and Debian keeps it for as long as the release is supported, adding the security fixes to it. For a WordPress site that didn't make any difference in my test. Even the http2 on directive, which nginx added in version 1.25.1, loads fine.

Ubuntu took these folders from Debian: sites-available, sites-enabled, conf.d and snippets/fastcgi-php.conf. So the server block goes where the Ubuntu guide puts it, and only the PHP lines change:

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}

Then you link the site, test it and reload nginx, the same as on Ubuntu. How to set up nginx for WooCommerce explains every line of a complete nginx config file.

root@server:~# ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
root@server:~# rm /etc/nginx/sites-enabled/default
root@server:~# nginx -t && systemctl reload nginx

Why nginx -t passes but the site shows a 502 error

That fastcgi_pass line is where my 502 came from, because I'd pasted the Ubuntu socket path into the Debian server block. Why did the test pass? nginx -t only checks that the configuration is valid. It doesn't look for the socket, so nginx reloaded without an error and the first page view failed:

root@server:~# grep fastcgi_pass /etc/nginx/sites-available/example.com
        fastcgi_pass unix:/run/php/php8.5-fpm.sock;
root@server:~# nginx -t && systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
root@server:~# curl -s -o /dev/null -w '%{http_code}\n' http://localhost/
502
root@server:~# tail -n 1 /var/log/nginx/error.log
2026/10/28 00:18:57 [crit] 19153#19153: *9 connect() to unix:/run/php/php8.5-fpm.sock failed (2: No such file or directory) while connecting to upstream, client: ::1, server: example.com, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.5-fpm.sock:", host: "localhost"
root@server:~# ls /run/php/
php-fpm.sock
php8.4-fpm.pid
php8.4-fpm.sock

The ls output shows the fix. Next to php8.4-fpm.sock there's php-fpm.sock, a link without a version number. The PHP-FPM service creates it when it starts and points it at its own socket. With fastcgi_pass unix:/run/php/php-fpm.sock, my Debian test server returned 200. Ubuntu builds its PHP packages from the same Debian packaging, so the same link should be there too, but I only tested it on Debian: run ls /run/php/ on your Ubuntu server before you rely on it.

That's the name I'd use while the server has one PHP version. With a second PHP from sury.org the link moved: it pointed at Debian's version, then at the newer one after I set update-alternatives back to automatic mode, then at Debian's again after a reboot. A server with two versions needs the path with the version in it. If the socket path is correct and you still get a 502 error, my 502 Bad Gateway article explains the other causes, each with its error.log line.

The default site has one more problem, on Debian and Ubuntu alike: its commented-out PHP example still points at php7.4-fpm.sock, a version neither system has included for years.

nginx from nginx.org uses different folders

If you want the newest nginx on Debian, nginx.org runs its own Debian repository, and its package is built differently. I installed nginx.org's current stable release, 1.30.6, in a new Debian container and put the same server block into conf.d. That package doesn't have a sites-available or a snippets folder, so the line include snippets/fastcgi-php.conf from Debian's layout failed. After I'd fixed that, the next error was about permissions: nginx.org's package runs nginx as the user nginx, and that user isn't allowed to open a PHP socket that belongs to www-data:

nginx: [emerg] open() "/etc/nginx/snippets/fastcgi-php.conf" failed (2: No such file or directory) in /etc/nginx/conf.d/example.com.conf:18
...
[crit] 10000#10000: *1 connect() to unix:/run/php/php8.4-fpm.sock failed (13: Permission denied) while connecting to upstream

In that output I left out the lines between the two errors and shortened the second error line. Then I got nginx.org's package working with PHP: I replaced the snippet with include fastcgi_params; and a fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; line, added the nginx user to the www-data group with usermod -aG www-data nginx and restarted nginx.

For a store I'd keep Debian's own nginx. Commands from Ubuntu guides work with it, and its fixes come with apt upgrade like everything else.

MariaDB 11.8: the same version on Debian and Ubuntu

I didn't have to change anything for the database. Both systems include MariaDB 11.8.6, and WordPress asks for 10.11 or greater. On Debian, root logs in through the Unix socket without a password (the root account is defined as IDENTIFIED VIA mysql_native_password USING 'invalid' OR unix_socket), new databases default to utf8mb4, and the usual CREATE DATABASE and CREATE USER commands ran unchanged.

One old name is gone on both systems: mysql_secure_installation. MariaDB includes that script only as mariadb-secure-installation now, while the mysql and mysqldump commands are still there. If a command from an older guide isn't found, try it with the mariadb- prefix.

Set up a firewall on Debian 13

Ubuntu includes ufw and leaves it off. Debian doesn't include ufw or any rules: its cloud image has only the iptables package, and even where the nftables package is installed, its service is disabled and /etc/nftables.conf holds chains with no rules in them. So every port that a program listens on is open.

Allow SSH before you enable any firewall, or the firewall also blocks your own SSH connection.

Which tool should you pick? The quickest is ufw, because Debian's ufw has the same application profiles as Ubuntu's (OpenSSH and Nginx Full both exist), so the Ubuntu commands work as soon as the package is installed. Over SSH, ufw enable asks you to confirm with y first:

root@server:~# apt install ufw
root@server:~# ufw allow OpenSSH
root@server:~# ufw allow 'Nginx Full'
root@server:~# ufw enable
Firewall is active and enabled on system startup

Or don't install ufw, and write the ruleset nftables loads at boot. On a server made from the cloud image, install nftables first with apt install nftables. This /etc/nftables.conf allows SSH, HTTP and HTTPS, answers ping and drops everything else:

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
    chain input {
        type filter hook input priority filter; policy drop;

        iif "lo" accept
        ct state established,related accept
        ct state invalid drop

        meta l4proto { icmp, ipv6-icmp } accept

        tcp dport 22 accept
        tcp dport { 80, 443 } accept
    }
    chain forward {
        type filter hook forward priority filter; policy drop;
    }
    chain output {
        type filter hook output priority filter; policy accept;
    }
}
root@server:~# nft -c -f /etc/nftables.conf
root@server:~# systemctl enable --now nftables
root@server:~# systemctl is-enabled nftables
enabled

nft -c only checks the file, and no output means no errors. I'd still use ufw on a single web server, because whoever manages the server after you will know the ufw commands.

Automatic security updates depend on how the server was installed

Ubuntu Server installs unattended-upgrades and turns on automatic security updates right after installation, as its documentation says. Debian's installer has done the opposite since 2018: at the request of the Debian security team, its update setting defaults to "No automatic updates", and a default install doesn't even show the question.

Debian's cloud team made the other choice for its images, which include unattended-upgrades, and on Debian 13 that package switches itself on when it's installed. So a server made from the cloud image and one made with the installer behave in opposite ways.

Which one did your provider give you? A line ending in "1" means automatic updates are on, and no line at all means the package isn't installed:

root@server:~# apt-config dump APT::Periodic::Unattended-Upgrade
APT::Periodic::Unattended-Upgrade "1";

If the command prints nothing, the package isn't there, so run apt install unattended-upgrades and then run the check again. On my test server that one command turned automatic updates on: the daily timer was scheduled, and the allowed list held only Debian's own origins, including the security archive. On a server made with the installer the check can print "0", because the installer saves the answer "No automatic updates" for this package before the package is ever installed. If you see "0", or want to change the setting later, run dpkg-reconfigure unattended-upgrades and answer Yes.

Install Certbot with apt (Debian has no snap)

Certbot's own instructions install it with snap, and Debian doesn't have snapd unless you add it. I'd use Debian's package instead: Certbot 4.0.0 from the main archive, the same version Ubuntu has in apt, while the snap has 5.8.0, released at the start of September.

root@server:~# apt install certbot python3-certbot-nginx
root@server:~# certbot --version
certbot 4.0.0

The package renews certificates with a systemd timer set for midnight and noon, and each run waits a random time of up to 12 hours. systemctl list-timers certbot.timer shows when it'll run next. How to set up Certbot on nginx shows how to get the first certificate, including when the store is behind Cloudflare.

When the store works over HTTPS, test its speed from another computer. My test server sent the WooCommerce home page in about 110 milliseconds without a page cache, but that request came from the server itself, and a shopper's request has to travel over the internet first.

Store Speed Test

Check how fast your new Debian server responds to shoppers

Paste a product page address and see its three Core Web Vitals scores and the total delay. A free account names the 5 apps that cost the most time, with the fix for your platform.

Test my store

How I tested Debian 13, and what a container test can't check

I tested in a container from the official debian:13 Docker image (not the cloud image from the table), Debian 13.7, on October 28, 2026, and ran every command as root, except the su test, where I logged in as a user named shop. A plain container doesn't run systemd, so I ran the install twice: once with the old service command, and once in a second container that boots systemd, where the systemctl, ufw and nftables commands really ran.

The Docker image also stops packages from starting their services during an install (a small policy-rc.d file does that), so I removed that file to make the second container behave like a server. The Ubuntu 26.04.1 container was only for apt-cache policy and for reading what's inside its packages; I installed nothing on it.

A container can't run the Debian installer either, so what I say about sudo and update defaults on an installed server comes from Debian's installation guide and the installer's source code. These are the versions I had at the end:

  • Debian 13.7, PHP 8.4.27, nginx 1.26.3 and MariaDB 11.8.6 from Debian's archive.
  • WordPress 7.1.3, WooCommerce 11.2.1 and WP-CLI 2.12.0.
  • For the comparisons: Ubuntu 26.04.1, PHP 8.5.12 from sury.org and nginx 1.30.6 from nginx.org.

On a real server, check the services after the first reboot, before the store takes orders:

root@server:~# systemctl is-active nginx php8.4-fpm mariadb nftables
active
active
active
active

I restarted my systemd container to get that output. If you chose ufw, write ufw in place of nftables in that command. Four lines that say active mean the web server, PHP, the database and the firewall all started again after a reboot with nobody logged in to start them, and that's the last thing I check before a store opens on a new server.

Sources

Debian, Debian packages, Ubuntu and 3 more
6
Learning CenterModule 18/20Hosting & Security

This guide is part of the free CartHacker Learning Center. Create an account to track your reading progress and keep your place across devices, with playbooks matched to your store's platform.

Progress tracking is paused0%
Mark as Read
19Up NextHow Fast Are Online Stores? Speed Data by Platform 19 min readTrends & Strategy

Discussion 0

Ask a question or share what you saw on your own store.

No comments yet.