<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SᕼᗩᕼIᗪ KᗩᖇIᗰI</title>
    <description>The latest articles on DEV Community by SᕼᗩᕼIᗪ KᗩᖇIᗰI (@shahid_karimi).</description>
    <link>https://dev.to/shahid_karimi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F73071%2F1b332f6a-a6f9-4d5a-ae07-780f087c6158.jpg</url>
      <title>DEV Community: SᕼᗩᕼIᗪ KᗩᖇIᗰI</title>
      <link>https://dev.to/shahid_karimi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shahid_karimi"/>
    <language>en</language>
    <item>
      <title>How to Install Moodle 5 on Ubuntu 24.04 (Nginx, PHP 8.3 and PostgreSQL)</title>
      <dc:creator>SᕼᗩᕼIᗪ KᗩᖇIᗰI</dc:creator>
      <pubDate>Sat, 31 Oct 2026 16:45:12 +0000</pubDate>
      <link>https://dev.to/shahid_karimi/how-to-install-moodle-5-on-ubuntu-2404-nginx-php-83-and-postgresql-1c6n</link>
      <guid>https://dev.to/shahid_karimi/how-to-install-moodle-5-on-ubuntu-2404-nginx-php-83-and-postgresql-1c6n</guid>
      <description>&lt;p&gt;Moodle is the most widely used open-source learning platform, and Moodle 5 changed how it is installed. Since &lt;strong&gt;Moodle 5.1&lt;/strong&gt;, the code is split into a web-facing &lt;code&gt;public/&lt;/code&gt; directory and private files above it, third-party libraries come from &lt;strong&gt;Composer&lt;/strong&gt;, and the web server must be configured for Moodle's &lt;strong&gt;router&lt;/strong&gt;. Most Moodle tutorials online predate all three changes.&lt;/p&gt;

&lt;p&gt;This guide installs Moodle 5.1 or 5.2 on &lt;strong&gt;Ubuntu 24.04 LTS&lt;/strong&gt; with &lt;strong&gt;Nginx, PHP 8.3 and PostgreSQL 16&lt;/strong&gt; — all from Ubuntu's own archive, no third-party repositories needed — and covers the parts that decide whether Moodle runs well: &lt;code&gt;moodledata&lt;/code&gt;, file permissions, &lt;code&gt;max_input_vars&lt;/code&gt;, cron and HTTPS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; install Nginx, PHP 8.3-FPM with Moodle's extensions and PostgreSQL; clone the Moodle stable branch and run &lt;code&gt;composer install --no-dev --classmap-authoritative&lt;/code&gt;; create &lt;code&gt;moodledata&lt;/code&gt; outside the web root; point Nginx's root at Moodle's &lt;code&gt;public/&lt;/code&gt; directory with a fallback to &lt;code&gt;r.php&lt;/code&gt;; finish the installer; set &lt;code&gt;$CFG-&amp;gt;routerconfigured = true;&lt;/code&gt;; and run Moodle's cron every minute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Moodle 5 requirements
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Moodle 5.1&lt;/th&gt;
&lt;th&gt;Moodle 5.2&lt;/th&gt;
&lt;th&gt;On Ubuntu 24.04&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;8.2 – 8.4&lt;/td&gt;
&lt;td&gt;8.3 or newer&lt;/td&gt;
&lt;td&gt;PHP 8.3 is the default — fits both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;14 or newer&lt;/td&gt;
&lt;td&gt;16 or newer&lt;/td&gt;
&lt;td&gt;PostgreSQL 16 is the default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MariaDB / MySQL&lt;/td&gt;
&lt;td&gt;MariaDB 10.11 / MySQL 8.4&lt;/td&gt;
&lt;td&gt;Check the release notes&lt;/td&gt;
&lt;td&gt;MariaDB 10.11 is the default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP &lt;code&gt;max_input_vars&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;5000 or more&lt;/td&gt;
&lt;td&gt;5000 or more&lt;/td&gt;
&lt;td&gt;Default is 1000 — must be raised&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web server&lt;/td&gt;
&lt;td&gt;Document root = &lt;code&gt;public/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Document root = &lt;code&gt;public/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Nginx or Apache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;PHP 8.3 and PostgreSQL 16 from Ubuntu 24.04 satisfy both Moodle 5.1 and 5.2, which is why this guide uses them. Before upgrading or installing a newer branch, check &lt;em&gt;Site administration → Server → Environment&lt;/em&gt; or the release notes on moodle.org.&lt;/p&gt;

&lt;p&gt;PostgreSQL is Moodle's best-tested database and the one Moodle HQ recommends; MariaDB works too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: install Nginx, PHP 8.3 and PostgreSQL
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;nginx postgresql git unzip composer &lt;span class="se"&gt;\&lt;/span&gt;
  php8.3-fpm php8.3-cli php8.3-pgsql php8.3-curl php8.3-gd php8.3-intl &lt;span class="se"&gt;\&lt;/span&gt;
  php8.3-mbstring php8.3-xml php8.3-zip php8.3-soap php8.3-opcache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Raise the PHP limits Moodle checks, in &lt;strong&gt;both&lt;/strong&gt; &lt;code&gt;/etc/php/8.3/fpm/php.ini&lt;/code&gt; and &lt;code&gt;/etc/php/8.3/cli/php.ini&lt;/code&gt; (cron and the CLI tools use the second one):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;max_input_vars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;5000&lt;/span&gt;
&lt;span class="py"&gt;memory_limit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;256M&lt;/span&gt;
&lt;span class="py"&gt;upload_max_filesize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;100M&lt;/span&gt;
&lt;span class="py"&gt;post_max_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;100M&lt;/span&gt;
&lt;span class="py"&gt;opcache.enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;opcache.memory_consumption&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;128&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart php8.3-fpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: create the database
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres psql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;moodle&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'a-long-random-password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;moodle&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;OWNER&lt;/span&gt; &lt;span class="n"&gt;moodle&lt;/span&gt; &lt;span class="k"&gt;ENCODING&lt;/span&gt; &lt;span class="s1"&gt;'UTF8'&lt;/span&gt; &lt;span class="k"&gt;TEMPLATE&lt;/span&gt; &lt;span class="n"&gt;template0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL on Ubuntu listens only on localhost by default, which is exactly what a single-server Moodle wants.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: download Moodle and install its dependencies
&lt;/h2&gt;

&lt;p&gt;Use Git so later updates are a &lt;code&gt;git pull&lt;/code&gt; rather than a re-download. Pick the newest stable branch listed on moodle.org — &lt;code&gt;MOODLE_502_STABLE&lt;/code&gt; for Moodle 5.2, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 &lt;span class="nt"&gt;--branch&lt;/span&gt; MOODLE_502_STABLE https://github.com/moodle/moodle.git /var/www/moodle
&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/moodle
&lt;span class="nb"&gt;sudo &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt; &lt;span class="nt"&gt;--classmap-authoritative&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Composer step is new in Moodle 5.1: a Git checkout no longer contains the &lt;code&gt;vendor/&lt;/code&gt; directory, and Moodle refuses to run without it.&lt;/p&gt;

&lt;p&gt;Keep the code owned by &lt;code&gt;root&lt;/code&gt; and &lt;strong&gt;not writable by the web server&lt;/strong&gt;. Moodle never needs to write to its own code; if PHP cannot modify it, an attacker who finds a vulnerability cannot either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; root:root /var/www/moodle
&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; a+rX /var/www/moodle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: create moodledata outside the web root
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;moodledata&lt;/code&gt; holds uploaded files, caches and sessions. It must &lt;strong&gt;never&lt;/strong&gt; be reachable over HTTP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /var/moodledata
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;www-data:www-data /var/moodledata
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;0770 /var/moodledata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: configure Nginx for the public directory and the router
&lt;/h2&gt;

&lt;p&gt;Since Moodle 5.1, Nginx must serve &lt;strong&gt;&lt;code&gt;/var/www/moodle/public&lt;/code&gt;&lt;/strong&gt; — not the repository root — and send requests for paths that are not files to Moodle's router, &lt;code&gt;r.php&lt;/code&gt;. Create &lt;code&gt;/etc/nginx/sites-available/moodle&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;lms.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/moodle/public&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;100M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/r.php&lt;/span&gt;&lt;span class="nv"&gt;$is_args$args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;[^/]\.php(/|$)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_split_path_info&lt;/span&gt; &lt;span class="s"&gt;^(.+&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.php)(/.*)&lt;/span&gt;$&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(!-f&lt;/span&gt; &lt;span class="nv"&gt;$document_root$fastcgi_script_name&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;fastcgi_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class="nv"&gt;$document_root$fastcgi_script_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;PATH_INFO&lt;/span&gt; &lt;span class="nv"&gt;$fastcgi_path_info&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="s"&gt;unix:/run/php/php8.3-fpm.sock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_read_timeout&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Hidden files (.git, .env and friends) are never served.&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;/\.&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;deny&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;PATH_INFO&lt;/code&gt; lines keep Moodle's "slash arguments" URLs (such as &lt;code&gt;/pluginfile.php/12/…&lt;/code&gt;) working.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/moodle /etc/nginx/sites-enabled/
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/nginx/sites-enabled/default
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: add HTTPS
&lt;/h2&gt;

&lt;p&gt;Point &lt;code&gt;lms.example.com&lt;/code&gt; at the server, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;certbot python3-certbot-nginx
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; lms.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do this &lt;strong&gt;before&lt;/strong&gt; running the installer, so Moodle records an &lt;code&gt;https://&lt;/code&gt; address as its &lt;code&gt;wwwroot&lt;/code&gt; from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: run the installer
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;https://lms.example.com&lt;/code&gt; and follow the web installer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data directory: &lt;code&gt;/var/moodledata&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Database driver: &lt;strong&gt;PostgreSQL (native/pgsql)&lt;/strong&gt;, host &lt;code&gt;localhost&lt;/code&gt;, database &lt;code&gt;moodle&lt;/code&gt;, user &lt;code&gt;moodle&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Because the code is not writable by PHP, the installer shows the contents of &lt;code&gt;config.php&lt;/code&gt; instead of writing it. Save that text as &lt;code&gt;/var/www/moodle/config.php&lt;/code&gt; — in the base directory, &lt;strong&gt;above&lt;/strong&gt; &lt;code&gt;public/&lt;/code&gt;, not inside it.&lt;/li&gt;
&lt;li&gt;Continue: Moodle checks the environment, installs its tables and asks you to create the first administrator.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then enable the router in &lt;code&gt;config.php&lt;/code&gt;, above the &lt;code&gt;require_once&lt;/code&gt; line at the end of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$CFG&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;routerconfigured&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flag tells Moodle that the web server sends unknown paths to &lt;code&gt;r.php&lt;/code&gt;. The Nginx rule and the flag must agree: with the rule but not the flag, routed pages return 404s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: schedule Moodle's cron
&lt;/h2&gt;

&lt;p&gt;Moodle's cron sends notifications, runs scheduled and ad-hoc tasks, and cleans up caches. Run it every minute as the web server user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;crontab &lt;span class="nt"&gt;-u&lt;/span&gt; www-data &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* * * * * /usr/bin/php8.3 /var/www/moodle/public/admin/cli/cron.php &amp;gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Moodle 5.1 and later the CLI scripts live under &lt;code&gt;public/admin/cli/&lt;/code&gt;; on Moodle 5.0 and earlier the path has no &lt;code&gt;public/&lt;/code&gt;. Run &lt;code&gt;ls /var/www/moodle/public/admin/cli/cron.php&lt;/code&gt; once to confirm it on your installation. Check &lt;em&gt;Site administration → Notifications&lt;/em&gt; after a few minutes — Moodle warns there if cron has not run recently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9: production hardening
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caching:&lt;/strong&gt; a Redis-compatible store (Redis or Valkey) for the application cache and sessions makes a large difference once more than a handful of people use the site. Configure it under &lt;em&gt;Site administration → Plugins → Caching&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups:&lt;/strong&gt; back up the PostgreSQL database &lt;em&gt;and&lt;/em&gt; &lt;code&gt;/var/moodledata&lt;/code&gt; together; one without the other cannot be restored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updates:&lt;/strong&gt; &lt;code&gt;cd /var/www/moodle &amp;amp;&amp;amp; sudo git pull &amp;amp;&amp;amp; sudo composer install --no-dev --classmap-authoritative&lt;/code&gt;, then complete the upgrade at &lt;em&gt;Site administration → Notifications&lt;/em&gt; or with the CLI upgrade script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email:&lt;/strong&gt; configure outgoing SMTP under &lt;em&gt;Site administration → Server → Email&lt;/em&gt;, or users never receive password resets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running Moodle next to your other PHP apps
&lt;/h2&gt;

&lt;p&gt;Moodle is not yet a one-click application in &lt;a href="https://hanoo.io/" rel="noopener noreferrer"&gt;Hanoo&lt;/a&gt; — today's catalogue covers Laravel, WordPress, Magento, Drupal and plain PHP. If your team runs those alongside an LMS, Hanoo can manage them on servers you own with PHP 8.0–8.5 side by side, per-application users and PHP-FPM pools, MariaDB, domains with automatic HTTPS, cron jobs and Git deploys — while Moodle follows the manual setup above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Which PHP version should I use for Moodle 5?
&lt;/h3&gt;

&lt;p&gt;PHP 8.3. It is supported by both Moodle 5.1 (PHP 8.2–8.4) and Moodle 5.2 (PHP 8.3 and newer), and it is the default PHP on Ubuntu 24.04, so no extra repository is needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the public directory in Moodle 5.1?
&lt;/h3&gt;

&lt;p&gt;From Moodle 5.1, files that browsers may request live in &lt;code&gt;public/&lt;/code&gt;, while &lt;code&gt;config.php&lt;/code&gt;, &lt;code&gt;vendor/&lt;/code&gt; and other private files stay in the directory above it. The web server's document root must point at &lt;code&gt;public/&lt;/code&gt;, which keeps configuration and dependencies out of reach of HTTP requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does Moodle show 404 errors after installing on Nginx?
&lt;/h3&gt;

&lt;p&gt;Usually the router is not configured. Make sure Nginx's &lt;code&gt;location /&lt;/code&gt; block ends with &lt;code&gt;try_files $uri $uri/ /r.php$is_args$args;&lt;/code&gt;, that the PHP location passes &lt;code&gt;PATH_INFO&lt;/code&gt;, and that &lt;code&gt;config.php&lt;/code&gt; contains &lt;code&gt;$CFG-&amp;gt;routerconfigured = true;&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use PostgreSQL or MariaDB for Moodle?
&lt;/h3&gt;

&lt;p&gt;Both are supported. PostgreSQL is Moodle's most thoroughly tested database and is a good default for new installations; on Ubuntu 24.04, PostgreSQL 16 from the default archive meets Moodle 5's requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why must moodledata be outside the web root?
&lt;/h3&gt;

&lt;p&gt;It contains every uploaded file, cached data and session. If it were inside the web root, those files could be downloaded directly, bypassing Moodle's permission checks entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often should Moodle cron run?
&lt;/h3&gt;

&lt;p&gt;Every minute. Moodle decides internally which scheduled tasks are due, so a one-minute cron keeps notifications, forum emails and background tasks timely without overloading the server.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://hanoo.io/blog/install-moodle-5-ubuntu-24-04" rel="noopener noreferrer"&gt;Hanoo blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>moodle</category>
      <category>php</category>
      <category>ubuntu</category>
      <category>nginx</category>
    </item>
    <item>
      <title>How to Install PHP 8.5 on Ubuntu 24.04 — and Run Several PHP Versions Side by Side</title>
      <dc:creator>SᕼᗩᕼIᗪ KᗩᖇIᗰI</dc:creator>
      <pubDate>Sat, 31 Oct 2026 16:44:48 +0000</pubDate>
      <link>https://dev.to/shahid_karimi/how-to-install-php-85-on-ubuntu-2404-and-run-several-php-versions-side-by-side-1egh</link>
      <guid>https://dev.to/shahid_karimi/how-to-install-php-85-on-ubuntu-2404-and-run-several-php-versions-side-by-side-1egh</guid>
      <description>&lt;p&gt;PHP 8.5 was released in November 2025 and is now the newest supported PHP. Ubuntu 24.04 LTS, though, ships &lt;strong&gt;PHP 8.3&lt;/strong&gt; in its own archive — so getting PHP 8.5 means adding a package source, and running real applications usually means keeping &lt;strong&gt;several PHP versions installed side by side&lt;/strong&gt;: Magento on 8.5, a Laravel app on 8.4, a legacy site on 8.3.&lt;/p&gt;

&lt;p&gt;This guide installs PHP 8.5 and PHP-FPM on Ubuntu 24.04, runs it next to other versions without conflicts, gives each site its own PHP-FPM pool, and keeps everything patched.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; add the &lt;code&gt;ppa:ondrej/php&lt;/code&gt; repository, install &lt;code&gt;php8.5-fpm&lt;/code&gt; plus the extensions your app needs, and create one FPM pool per site pointing at the PHP version that site needs. Versions install side by side under &lt;code&gt;/etc/php/&amp;lt;version&amp;gt;/&lt;/code&gt;; the &lt;code&gt;php&lt;/code&gt; command is chosen with &lt;code&gt;update-alternatives&lt;/code&gt;, but scripts and cron jobs should call &lt;code&gt;php8.5&lt;/code&gt; explicitly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Which PHP versions are supported in 2026?
&lt;/h2&gt;

&lt;p&gt;Each PHP release gets two years of active support followed by two years of security fixes, ending on 31 December:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Released&lt;/th&gt;
&lt;th&gt;Active support until&lt;/th&gt;
&lt;th&gt;Security fixes until&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.5&lt;/td&gt;
&lt;td&gt;November 2025&lt;/td&gt;
&lt;td&gt;31 Dec 2027&lt;/td&gt;
&lt;td&gt;31 Dec 2029&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.4&lt;/td&gt;
&lt;td&gt;November 2024&lt;/td&gt;
&lt;td&gt;31 Dec 2026&lt;/td&gt;
&lt;td&gt;31 Dec 2028&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.3&lt;/td&gt;
&lt;td&gt;November 2023&lt;/td&gt;
&lt;td&gt;Ended&lt;/td&gt;
&lt;td&gt;31 Dec 2027&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.2&lt;/td&gt;
&lt;td&gt;December 2022&lt;/td&gt;
&lt;td&gt;Ended&lt;/td&gt;
&lt;td&gt;31 Dec 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.1 and older&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Ended&lt;/td&gt;
&lt;td&gt;Ended — upgrade&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For new projects, choose PHP 8.4 or 8.5. Check your framework first: Magento 2.4.9 wants 8.4 or 8.5, Laravel 13 needs 8.3 or newer, WordPress recommends 8.4 or 8.5, and Moodle 5.1 stops at 8.4.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's new in PHP 8.5
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The pipe operator &lt;code&gt;|&amp;gt;&lt;/code&gt;&lt;/strong&gt; passes a value through a chain of callables left to right, replacing deeply nested calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A built-in URI extension&lt;/strong&gt; parses and normalises URLs according to RFC 3986 and the WHATWG URL standard, instead of the loose &lt;code&gt;parse_url()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;array_first()&lt;/code&gt; and &lt;code&gt;array_last()&lt;/code&gt;&lt;/strong&gt; finally return the first and last values of an array without moving its internal pointer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;clone&lt;/code&gt; with property overrides&lt;/strong&gt;, which makes "with-er" methods on readonly classes much simpler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#[\NoDiscard]&lt;/code&gt;&lt;/strong&gt; warns when the return value of a function that must be checked is ignored.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'  Hello World  '&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;strtolower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// "hello-world"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: add the PHP repository
&lt;/h2&gt;

&lt;p&gt;The Ondřej Surý PPA is the de-facto source of current PHP builds for Ubuntu and Debian; it is maintained by a long-standing Debian PHP packager and carries every supported version for each Ubuntu LTS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;software-properties-common
&lt;span class="nb"&gt;sudo &lt;/span&gt;add-apt-repository ppa:ondrej/php
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: install PHP 8.5 and PHP-FPM
&lt;/h2&gt;

&lt;p&gt;Install the FPM service, the command-line binary and the extensions your application needs. A broad set that covers most frameworks and CMSs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;php8.5-fpm php8.5-cli php8.5-common php8.5-mysql php8.5-pgsql &lt;span class="se"&gt;\&lt;/span&gt;
  php8.5-curl php8.5-gd php8.5-intl php8.5-mbstring php8.5-xml php8.5-zip &lt;span class="se"&gt;\&lt;/span&gt;
  php8.5-bcmath php8.5-soap php8.5-opcache php8.5-redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php8.5 &lt;span class="nt"&gt;-v&lt;/span&gt;
systemctl status php8.5-fpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In PHP 8.5, OPcache is always compiled in rather than being a separately loaded extension, so a &lt;code&gt;php8.5-opcache&lt;/code&gt; package may not be needed. If &lt;code&gt;apt&lt;/code&gt; reports it does not exist, OPcache is already there — confirm with &lt;code&gt;php8.5 -i | grep opcache.enable&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3: install other versions side by side
&lt;/h2&gt;

&lt;p&gt;Every version lives in its own directories and runs its own service, so installing another one never replaces the first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;php8.4-fpm php8.4-cli php8.4-mysql php8.4-mbstring php8.4-xml php8.4-curl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/php/8.4/fpm/   → php8.4-fpm.service → /run/php/php8.4-fpm.sock
/etc/php/8.5/fpm/   → php8.5-fpm.service → /run/php/php8.5-fpm.sock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each version has its own &lt;code&gt;php.ini&lt;/code&gt; for FPM and CLI, and its own extensions. Installing &lt;code&gt;php8.5-redis&lt;/code&gt; does nothing for PHP 8.4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: give each site its own PHP-FPM pool
&lt;/h2&gt;

&lt;p&gt;The default &lt;code&gt;www&lt;/code&gt; pool runs every site as &lt;code&gt;www-data&lt;/code&gt;. That means one compromised site can read every other site's files and secrets. Give each site a pool of its own, running as its own user, on the PHP version it needs. For a shop running on PHP 8.5, create &lt;code&gt;/etc/php/8.5/fpm/pool.d/shop.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[shop]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="py"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;shop&lt;/span&gt;
&lt;span class="py"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;shop&lt;/span&gt;
&lt;span class="py"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/run/php/php8.5-fpm-shop.sock&lt;/span&gt;
&lt;span class="py"&gt;listen.owner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;www-data&lt;/span&gt;
&lt;span class="py"&gt;listen.group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;www-data&lt;/span&gt;
&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="py"&gt;pm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;dynamic&lt;/span&gt;
&lt;span class="py"&gt;pm.max_children&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;12&lt;/span&gt;
&lt;span class="py"&gt;pm.start_servers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;
&lt;span class="py"&gt;pm.min_spare_servers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;
&lt;span class="py"&gt;pm.max_spare_servers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;pm.max_requests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;500&lt;/span&gt;
&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;php_admin_value&lt;/span&gt;&lt;span class="nn"&gt;[memory_limit]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;512M&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;php_admin_value&lt;/span&gt;&lt;span class="nn"&gt;[open_basedir]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;/srv/shop:/tmp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;php_admin_flag&lt;/span&gt;&lt;span class="nn"&gt;[expose_php]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;off&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--group&lt;/span&gt; &lt;span class="nt"&gt;--home&lt;/span&gt; /srv/shop shop
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload php8.5-fpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point the site's web server at that socket — &lt;code&gt;fastcgi_pass unix:/run/php/php8.5-fpm-shop.sock;&lt;/code&gt; in Nginx, or &lt;code&gt;php_fastcgi unix//run/php/php8.5-fpm-shop.sock&lt;/code&gt; in Caddy. Moving the site to another PHP version later means creating the same pool under the other version and switching the socket path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sizing &lt;code&gt;pm.max_children&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Each FPM worker holds a full PHP process in memory. A practical starting point is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pm.max_children ≈ (RAM you can give this site) ÷ (average worker memory)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Measure the average with &lt;code&gt;ps -o rss= -u shop | awk '{ s += $1 } END { print s / NR / 1024 " MB" }'&lt;/code&gt; under normal traffic. 60–120 MB per worker is typical for WordPress and Laravel; Magento workers are larger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: choose the default &lt;code&gt;php&lt;/code&gt; command
&lt;/h2&gt;

&lt;p&gt;The plain &lt;code&gt;php&lt;/code&gt; command points to one version through Debian's alternatives system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;update-alternatives &lt;span class="nt"&gt;--config&lt;/span&gt; php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This affects only the CLI. FPM pools, cron jobs and deploy scripts should name the version explicitly (&lt;code&gt;php8.5 artisan …&lt;/code&gt;, &lt;code&gt;php8.4 wp-cli.phar …&lt;/code&gt;) so a change to the default never silently moves an application to another PHP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: keep PHP patched
&lt;/h2&gt;

&lt;p&gt;Ubuntu's &lt;code&gt;unattended-upgrades&lt;/code&gt; installs security updates from Ubuntu's own archive, but &lt;strong&gt;not from PPAs&lt;/strong&gt; unless you allow them. Add the PPA's origin in a file such as &lt;code&gt;/etc/apt/apt.conf.d/52unattended-upgrades-php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unattended-Upgrade::Allowed-Origins:: "LP-PPA-ondrej-php:${distro_codename}";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PHP patch releases come out roughly monthly and regularly include security fixes; leaving PHP on a PPA without automatic updates is one of the most common ways self-managed servers fall behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiple PHP versions with Hanoo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hanoo.io/" rel="noopener noreferrer"&gt;Hanoo&lt;/a&gt; manages exactly this setup on Ubuntu servers you own. It installs &lt;strong&gt;PHP 8.0 through 8.5 side by side&lt;/strong&gt; on Ubuntu 22.04, 24.04 and 26.04 LTS — from the same PPA on 22.04 and 24.04, and from Ondřej Surý's &lt;code&gt;packages.sury.org&lt;/code&gt; repository on 26.04, where the PPA publishes no builds — and adds that source to unattended upgrades, so PHP security releases arrive automatically. Every application gets its &lt;strong&gt;own Linux user and its own PHP-FPM pool&lt;/strong&gt; on the version you pick when you create it, and pool settings — process manager, &lt;code&gt;max_children&lt;/code&gt;, memory limit, upload size — are editable as structured fields in the dashboard rather than raw config files. Cron jobs and workers run as the application's user, so a Magento store on PHP 8.5 and a legacy app on PHP 8.1 share a server without sharing a single file permission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Ubuntu 24.04 include PHP 8.5?
&lt;/h3&gt;

&lt;p&gt;No. Ubuntu 24.04's own archive provides PHP 8.3. PHP 8.4 and 8.5 for Ubuntu 24.04 are installed from the &lt;code&gt;ppa:ondrej/php&lt;/code&gt; repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run PHP 8.3, 8.4 and 8.5 on the same server?
&lt;/h3&gt;

&lt;p&gt;Yes. Each version installs into its own directories with its own FPM service and socket, so they coexist. Point each site's web server configuration at the FPM socket of the version it needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I change the default PHP version on Ubuntu?
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;sudo update-alternatives --config php&lt;/code&gt; and choose the version. This changes the &lt;code&gt;php&lt;/code&gt; command only; web requests use whichever PHP-FPM socket the web server is configured with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is PHP 8.5 stable enough for production?
&lt;/h3&gt;

&lt;p&gt;Yes. PHP 8.5 is a general-availability release with active support until the end of 2027. The real question is whether your application and its dependencies support it — Magento 2.4.9 does, for example, while Moodle 5.1 stops at PHP 8.4.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should every site have its own PHP-FPM pool?
&lt;/h3&gt;

&lt;p&gt;Yes. A separate pool per site, running as a separate user, isolates sites from each other, lets you size and tune each one independently, and makes it possible to run different PHP versions per site.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I enable OPcache in PHP 8.5?
&lt;/h3&gt;

&lt;p&gt;OPcache is built into PHP 8.5. Make sure &lt;code&gt;opcache.enable=1&lt;/code&gt; in the FPM &lt;code&gt;php.ini&lt;/code&gt; and give it enough memory with &lt;code&gt;opcache.memory_consumption&lt;/code&gt;; on a production server, also consider &lt;code&gt;opcache.validate_timestamps=0&lt;/code&gt; with an FPM reload after every deploy.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://hanoo.io/blog/install-php-8-5-ubuntu-24-04" rel="noopener noreferrer"&gt;Hanoo blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>ubuntu</category>
      <category>linux</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Install Magento 2.4.9 on Ubuntu 24.04 (with OpenSearch 3 and PHP 8.5)</title>
      <dc:creator>SᕼᗩᕼIᗪ KᗩᖇIᗰI</dc:creator>
      <pubDate>Sat, 31 Oct 2026 16:44:19 +0000</pubDate>
      <link>https://dev.to/shahid_karimi/how-to-install-magento-249-on-ubuntu-2404-with-opensearch-3-and-php-85-495</link>
      <guid>https://dev.to/shahid_karimi/how-to-install-magento-249-on-ubuntu-2404-with-opensearch-3-and-php-85-495</guid>
      <description>&lt;p&gt;Magento Open Source 2.4.9 is the most demanding PHP application most teams will ever self-host. A working store needs a specific PHP version with a long list of extensions, a supported MariaDB or MySQL, a running OpenSearch cluster, Composer, a web server that understands Magento's &lt;code&gt;pub/&lt;/code&gt; layout, production-mode compilation, static content deployment and a cron job that runs every minute.&lt;/p&gt;

&lt;p&gt;This guide covers both routes on &lt;strong&gt;Ubuntu 24.04 LTS&lt;/strong&gt;: what a manual installation involves step by step, and how to get the same store running from a single form with Hanoo on a server you own. Either way, you end up with a real Magento store on your own infrastructure — no shared hosting, no proprietary platform.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; Magento 2.4.9 needs PHP 8.4 or 8.5, OpenSearch 3, a recent MariaDB or MySQL, Composer 2 and at least 2 CPU cores and 3 GB of RAM in practice. With Hanoo you pick &lt;em&gt;Magento Open Source&lt;/em&gt; when creating an application, and the platform installs PHP 8.5, MariaDB 11.8, OpenSearch 3.8, Magento itself and HTTPS for you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Magento 2.4.9 system requirements
&lt;/h2&gt;

&lt;p&gt;Adobe tests each Magento release against a specific set of services. For 2.4.9, the release that went generally available in May 2026, the stack looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What Magento 2.4.9 expects&lt;/th&gt;
&lt;th&gt;What Hanoo installs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Operating system&lt;/td&gt;
&lt;td&gt;Any supported Linux&lt;/td&gt;
&lt;td&gt;Ubuntu 22.04, 24.04 or 26.04 LTS (amd64 or arm64)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;8.4 or 8.5 (8.3 only to upgrade older stores)&lt;/td&gt;
&lt;td&gt;PHP 8.5 with its own FPM pool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;MySQL 8.4 or a supported MariaDB release&lt;/td&gt;
&lt;td&gt;MariaDB 11.8, one private database per store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;OpenSearch 3 (Elasticsearch is no longer supported)&lt;/td&gt;
&lt;td&gt;OpenSearch 3.8, bound to 127.0.0.1 only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web server&lt;/td&gt;
&lt;td&gt;Nginx or Apache&lt;/td&gt;
&lt;td&gt;Caddy, serving &lt;code&gt;pub/&lt;/code&gt; as the document root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composer&lt;/td&gt;
&lt;td&gt;Composer 2&lt;/td&gt;
&lt;td&gt;Handled during installation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware&lt;/td&gt;
&lt;td&gt;2 GB RAM is the documented floor; real stores need more&lt;/td&gt;
&lt;td&gt;Asks for 2 CPU cores, 3 GB RAM and 20 GB disk, and warns before installing on less&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Always check Adobe's own system requirements page for the exact database version matrix before a production launch — Adobe supports only the combinations it tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why OpenSearch matters
&lt;/h3&gt;

&lt;p&gt;Since Magento 2.4, a search engine is mandatory: catalog search, layered navigation and even some admin grids query it. Magento 2.4.9 targets &lt;strong&gt;OpenSearch 3&lt;/strong&gt;. OpenSearch runs on the JVM, so it is the single largest memory consumer on a small Magento server — the most common reason a Magento installation fails on a 1 GB or 2 GB VPS is OpenSearch never finishing its start-up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: install Magento 2.4.9 manually on Ubuntu 24.04
&lt;/h2&gt;

&lt;p&gt;The manual route is worth understanding even if you never take it, because it shows exactly what has to be true on the server. The outline below assumes a fresh Ubuntu 24.04 server and a user with &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install PHP 8.5 and the extensions Magento needs
&lt;/h3&gt;

&lt;p&gt;Ubuntu 24.04 ships PHP 8.3, which Magento 2.4.9 does not support for new stores. PHP 8.5 comes from the widely used Ondřej Surý PPA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;add-apt-repository ppa:ondrej/php
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;php8.5-fpm php8.5-cli php8.5-bcmath php8.5-curl php8.5-gd &lt;span class="se"&gt;\&lt;/span&gt;
  php8.5-intl php8.5-mbstring php8.5-mysql php8.5-soap php8.5-xml &lt;span class="se"&gt;\&lt;/span&gt;
  php8.5-zip php8.5-sockets php8.5-opcache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then raise the limits Magento's installer checks, in &lt;code&gt;/etc/php/8.5/fpm/php.ini&lt;/code&gt; and &lt;code&gt;/etc/php/8.5/cli/php.ini&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;memory_limit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;2G&lt;/span&gt;
&lt;span class="py"&gt;max_execution_time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;1800&lt;/span&gt;
&lt;span class="py"&gt;zlib.output_compression&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;On&lt;/span&gt;
&lt;span class="py"&gt;realpath_cache_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;10M&lt;/span&gt;
&lt;span class="py"&gt;realpath_cache_ttl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;7200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install the database
&lt;/h3&gt;

&lt;p&gt;Create a dedicated database and user. Never let Magento connect as &lt;code&gt;root&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;magento&lt;/span&gt; &lt;span class="nb"&gt;CHARACTER&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'magento'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'a-long-random-password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;magento&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'magento'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;FLUSH&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the MariaDB version you install is one Adobe lists for 2.4.9; the version in Ubuntu's own archive is usually older than Magento wants, so most teams add MariaDB's official repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Install and tune OpenSearch 3
&lt;/h3&gt;

&lt;p&gt;Install OpenSearch 3 from the official OpenSearch APT repository, then keep it private and sized for the machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/opensearch/opensearch.yml&lt;/span&gt;
&lt;span class="na"&gt;cluster.name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;magento&lt;/span&gt;
&lt;span class="na"&gt;network.host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1&lt;/span&gt;
&lt;span class="na"&gt;http.port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9200&lt;/span&gt;
&lt;span class="na"&gt;discovery.type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;single-node&lt;/span&gt;
&lt;span class="na"&gt;plugins.security.disabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the JVM heap explicitly (for example &lt;code&gt;-Xms512m&lt;/code&gt; and &lt;code&gt;-Xmx512m&lt;/code&gt; on a small store, more on a large catalogue), raise &lt;code&gt;vm.max_map_count&lt;/code&gt; to &lt;code&gt;262144&lt;/code&gt;, then &lt;code&gt;sudo systemctl enable --now opensearch&lt;/code&gt; and confirm &lt;code&gt;curl http://127.0.0.1:9200&lt;/code&gt; answers with a 3.x version. Disabling the security plugin is only acceptable because the service listens on &lt;code&gt;127.0.0.1&lt;/code&gt; — never expose an unauthenticated OpenSearch port to the internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Download Magento and install its dependencies
&lt;/h3&gt;

&lt;p&gt;Download the 2.4.9 release (from Adobe's Composer repository with your access keys, or the tagged source archive on GitHub), place it under a directory owned by a dedicated, unprivileged user, and run &lt;code&gt;composer install --no-dev&lt;/code&gt; inside it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Run the Magento installer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php8.5 bin/magento setup:install &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://shop.example.com/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1 &lt;span class="nt"&gt;--db-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;magento &lt;span class="nt"&gt;--db-user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;magento &lt;span class="nt"&gt;--db-password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'…'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--search-engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;opensearch &lt;span class="nt"&gt;--opensearch-host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1 &lt;span class="nt"&gt;--opensearch-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9200 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;en_US &lt;span class="nt"&gt;--currency&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;USD &lt;span class="nt"&gt;--timezone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTC &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--use-rewrites&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;--document-root-is-pub&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--admin-firstname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Store &lt;span class="nt"&gt;--admin-lastname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Owner &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--admin-email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;you@example.com &lt;span class="nt"&gt;--admin-user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;storeadmin &lt;span class="nt"&gt;--admin-password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'…'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Switch to production mode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php8.5 bin/magento deploy:mode:set production &lt;span class="nt"&gt;--skip-compilation&lt;/span&gt;
php8.5 bin/magento setup:di:compile
php8.5 bin/magento setup:static-content:deploy &lt;span class="nt"&gt;-f&lt;/span&gt; en_US
php8.5 bin/magento cache:flush
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compilation alone can take several minutes and well over a gigabyte of memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Configure the web server for &lt;code&gt;pub/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Point the document root at Magento's &lt;code&gt;pub/&lt;/code&gt; directory, not the project root. Magento's sample Nginx file (&lt;code&gt;nginx.conf.sample&lt;/code&gt;) shows the rules it expects, including the &lt;code&gt;static.php&lt;/code&gt; and &lt;code&gt;get.php&lt;/code&gt; fallbacks for versioned static assets and media. Skipping those fallbacks is the classic cause of a storefront that loads but looks completely unstyled.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. HTTPS, file permissions and cron
&lt;/h3&gt;

&lt;p&gt;Finally: issue a certificate, make sure only the Magento user can write to &lt;code&gt;var/&lt;/code&gt;, &lt;code&gt;generated/&lt;/code&gt;, &lt;code&gt;pub/static/&lt;/code&gt; and &lt;code&gt;pub/media/&lt;/code&gt;, and add Magento's cron (&lt;code&gt;bin/magento cron:run&lt;/code&gt;) to run every minute. Without cron, indexers, emails and scheduled tasks silently stop.&lt;/p&gt;

&lt;p&gt;That is eight stages, each with its own failure modes. The rest of this guide shows the shorter path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: install Magento 2.4.9 with Hanoo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hanoo.io/" rel="noopener noreferrer"&gt;Hanoo&lt;/a&gt; is a control plane for PHP servers you own. You connect a clean Ubuntu server once, and from then on applications, domains, certificates, databases and services are managed from the dashboard — while everything on the server stays standard Ubuntu, Caddy, PHP-FPM and MariaDB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: connect an Ubuntu server
&lt;/h3&gt;

&lt;p&gt;Create a server at any provider — DigitalOcean, Hetzner, AWS, Vultr or your own hardware — with &lt;strong&gt;Ubuntu 22.04, 24.04 or 26.04 LTS&lt;/strong&gt;, at least &lt;strong&gt;2 CPU cores, 3 GB of RAM and 20 GB of disk&lt;/strong&gt;. 4 GB of RAM or more is a comfortable starting point for a real catalogue.&lt;/p&gt;

&lt;p&gt;In the Hanoo dashboard, choose &lt;strong&gt;Connect server&lt;/strong&gt; and run the one-time command it shows on the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://app.hanoo.io/api/v1/install.sh &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo &lt;/span&gt;bash &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--url&lt;/span&gt; https://app.hanoo.io/api &lt;span class="nt"&gt;--token&lt;/span&gt; hne_••••••
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command installs a small agent that connects &lt;em&gt;out&lt;/em&gt; to Hanoo over mutually authenticated TLS. No inbound management port is opened, and no root password or private key is shared. Review the server's details and approve it in the dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: create a Magento application
&lt;/h3&gt;

&lt;p&gt;Open &lt;strong&gt;Applications → Create application&lt;/strong&gt; and choose &lt;strong&gt;Magento Open Source&lt;/strong&gt;. Give the store a name and pick the server you just connected. PHP 8.5 is selected for you, because it is the version this Magento release is installed with.&lt;/p&gt;

&lt;p&gt;When you create the application, Hanoo's agent on your server:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checks the server against Magento's minimums and warns you up front if the install is likely to fail.&lt;/li&gt;
&lt;li&gt;Installs &lt;strong&gt;OpenSearch 3.8&lt;/strong&gt;, bound to &lt;code&gt;127.0.0.1&lt;/code&gt; with a fixed JVM heap and the kernel setting it needs, and waits until it answers.&lt;/li&gt;
&lt;li&gt;Creates an isolated Linux user and a dedicated &lt;strong&gt;PHP 8.5-FPM pool&lt;/strong&gt; for the store.&lt;/li&gt;
&lt;li&gt;Creates a private &lt;strong&gt;MariaDB 11.8&lt;/strong&gt; database and user, and keeps the password out of process lists and logs.&lt;/li&gt;
&lt;li&gt;Downloads the pinned &lt;strong&gt;Magento 2.4.9&lt;/strong&gt; release, verifies its checksum and installs its dependencies.&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;setup:install&lt;/code&gt;, switches the store to &lt;strong&gt;production mode&lt;/strong&gt;, compiles dependency injection and deploys static content.&lt;/li&gt;
&lt;li&gt;Configures &lt;strong&gt;Caddy&lt;/strong&gt; to serve &lt;code&gt;pub/&lt;/code&gt; with Magento's static and media fallbacks, and refuses PHP execution inside &lt;code&gt;pub/media&lt;/code&gt; and &lt;code&gt;pub/static&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can follow every phase live on the application's page; the whole run typically takes several minutes, most of it Magento's own compilation. If a step fails, the page names the step and the reason — not just "installation failed".&lt;/p&gt;

&lt;p&gt;The store is immediately reachable on its temporary Hanoo address over HTTPS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: create your admin user
&lt;/h3&gt;

&lt;p&gt;The installer does not invent an admin password for you. Create the first admin account yourself over SSH, as the store's own user. Add your public key under the application's &lt;strong&gt;Access&lt;/strong&gt; tab, connect, and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /srv/hanoo/apps/&amp;lt;application-id&amp;gt;/current
php8.5 bin/magento admin:user:create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--admin-user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;storeadmin &lt;span class="nt"&gt;--admin-password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'a-long-unique-password'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--admin-email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;you@example.com &lt;span class="nt"&gt;--admin-firstname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Store &lt;span class="nt"&gt;--admin-lastname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Owner
php8.5 bin/magento info:adminuri
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;info:adminuri&lt;/code&gt; prints the admin path. Magento enables &lt;strong&gt;two-factor authentication&lt;/strong&gt; for the admin by default and emails the setup link, so configure outgoing mail before your first sign-in on a production store.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: add your domain and switch the base URL
&lt;/h3&gt;

&lt;p&gt;Under the application's &lt;strong&gt;Domains&lt;/strong&gt; tab, add &lt;code&gt;shop.example.com&lt;/code&gt;. Hanoo checks that its DNS points at your server, issues a Let's Encrypt certificate and serves the store on it.&lt;/p&gt;

&lt;p&gt;Magento stores its own base URLs in the database, so tell it about the new domain once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php8.5 bin/magento config:set web/unsecure/base_url https://shop.example.com/
php8.5 bin/magento config:set web/secure/base_url https://shop.example.com/
php8.5 bin/magento config:set web/secure/use_in_frontend 1
php8.5 bin/magento config:set web/secure/use_in_adminhtml 1
php8.5 bin/magento cache:flush
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: schedule Magento's cron
&lt;/h3&gt;

&lt;p&gt;Open the application's &lt;strong&gt;Cron jobs&lt;/strong&gt; tab and add an entry that runs every minute (&lt;code&gt;* * * * *&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php8.5 bin/magento cron:run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cron jobs run as the store's own user, in its current release directory, and by default a run is skipped while the previous one is still going, so a slow run is never stacked on top of itself. You can run the entry on demand and see its last result and output in the dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting a Magento 2.4.9 installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The installer stops at OpenSearch
&lt;/h3&gt;

&lt;p&gt;Almost always memory. OpenSearch needs its heap plus headroom, and Magento's compile step runs alongside it. On a 1-core, 1 GB server OpenSearch never answers; give the server at least 3 GB of RAM (Hanoo warns you before installing on anything smaller, and names the memory problem if OpenSearch does not start) and avoid running other heavy services on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The storefront loads without any styling
&lt;/h3&gt;

&lt;p&gt;Static assets are requested with a versioned prefix such as &lt;code&gt;/static/version1760000000/…&lt;/code&gt;. If the web server does not route missing files under &lt;code&gt;pub/static&lt;/code&gt; to &lt;code&gt;static.php&lt;/code&gt;, every stylesheet request returns a 404 page. Hanoo configures these routes for you; on a manual setup, copy them from Magento's &lt;code&gt;nginx.conf.sample&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  "There has been an error processing your request"
&lt;/h3&gt;

&lt;p&gt;Look in &lt;code&gt;var/report/&lt;/code&gt; for the report number shown on the page, and in &lt;code&gt;var/log/exception.log&lt;/code&gt;. After any code or configuration change in production mode, re-run &lt;code&gt;setup:di:compile&lt;/code&gt; and &lt;code&gt;setup:static-content:deploy&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Indexers say "Reindex required"
&lt;/h3&gt;

&lt;p&gt;Cron is not running. Check the cron job's last run in the dashboard, or run &lt;code&gt;php8.5 bin/magento indexer:status&lt;/code&gt; to see which indexers are behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I install Magento 2.4.9 on Ubuntu 24.04?
&lt;/h3&gt;

&lt;p&gt;Yes. Ubuntu 24.04 LTS is a solid base for Magento 2.4.9, but its built-in PHP 8.3 is not supported for new 2.4.9 stores, so you need PHP 8.4 or 8.5 from another source, plus OpenSearch 3 and a supported database. Hanoo installs all of these for you on Ubuntu 22.04, 24.04 or 26.04.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much RAM does Magento 2.4.9 need?
&lt;/h3&gt;

&lt;p&gt;Adobe documents 2 GB as a minimum, but that leaves no room for OpenSearch and compilation. Plan for at least 3 GB on a small store and 4–8 GB once you have real traffic and a larger catalogue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Magento 2.4.9 still support Elasticsearch?
&lt;/h3&gt;

&lt;p&gt;No. Magento 2.4.9 uses OpenSearch, with OpenSearch 3 as the target version. If you are upgrading from a store that used Elasticsearch, migrate to OpenSearch first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which PHP version should I use for Magento 2.4.9?
&lt;/h3&gt;

&lt;p&gt;PHP 8.4 or 8.5. PHP 8.3 is only meant as a stepping stone when upgrading an older store. Hanoo installs Magento 2.4.9 with PHP 8.5.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it safe to run Magento on my own VPS instead of managed Magento hosting?
&lt;/h3&gt;

&lt;p&gt;Yes, if the basics are right: a dedicated unprivileged user per store, search and database bound to localhost, HTTPS everywhere, admin two-factor authentication, and timely security patches. Running on a server you own also means no platform lock-in — with Hanoo, your store keeps running even if you stop using the dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I find my Magento admin URL?
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;php8.5 bin/magento info:adminuri&lt;/code&gt; in the store's directory. The admin path is randomised at installation so it is not trivially guessable.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://hanoo.io/blog/install-magento-2-4-9-ubuntu-24-04" rel="noopener noreferrer"&gt;Hanoo blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>magento</category>
      <category>php</category>
      <category>ubuntu</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>How to Install WordPress on Ubuntu 24.04 with Caddy, PHP 8.4 and MariaDB</title>
      <dc:creator>SᕼᗩᕼIᗪ KᗩᖇIᗰI</dc:creator>
      <pubDate>Sat, 31 Oct 2026 16:43:51 +0000</pubDate>
      <link>https://dev.to/shahid_karimi/how-to-install-wordpress-on-ubuntu-2404-with-caddy-php-84-and-mariadb-244k</link>
      <guid>https://dev.to/shahid_karimi/how-to-install-wordpress-on-ubuntu-2404-with-caddy-php-84-and-mariadb-244k</guid>
      <description>&lt;p&gt;WordPress still powers a huge share of the web, and the cheapest way to run it well is on a server you control. This guide installs &lt;strong&gt;WordPress 7.1 on Ubuntu 24.04 LTS&lt;/strong&gt; with the stack the WordPress Hosting Team recommends today — &lt;strong&gt;PHP 8.4, MariaDB and HTTPS&lt;/strong&gt; — served by &lt;strong&gt;Caddy&lt;/strong&gt;, the web server that obtains and renews TLS certificates on its own.&lt;/p&gt;

&lt;p&gt;You will see every command for a manual install, the security settings most tutorials skip, and how to get the same result in a few minutes with Hanoo.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; install PHP 8.4-FPM with WordPress's extensions, create a MariaDB database and user, download WordPress, write &lt;code&gt;wp-config.php&lt;/code&gt; with unique salts, give the site its own PHP-FPM pool and Linux user, and let Caddy serve it with automatic HTTPS. Then block &lt;code&gt;wp-config.php&lt;/code&gt; and PHP inside &lt;code&gt;uploads/&lt;/code&gt;, and add an object cache.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  WordPress requirements in 2026
&lt;/h2&gt;

&lt;p&gt;WordPress's &lt;em&gt;minimum&lt;/em&gt; requirements are deliberately low (PHP 7.4 still runs it), but minimums are not what you want for a new site. The WordPress Hosting Team's recommendations for WordPress 7.1 are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Recommended for new sites&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;8.4 or 8.5&lt;/td&gt;
&lt;td&gt;8.3 works; PHP 8.2 reaches end of life on 31 December 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;MariaDB 10.11, 11.4 or 11.8, or MySQL 8.4&lt;/td&gt;
&lt;td&gt;Ubuntu 24.04's own &lt;code&gt;mariadb-server&lt;/code&gt; is 10.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web server&lt;/td&gt;
&lt;td&gt;Any server with HTTPS&lt;/td&gt;
&lt;td&gt;Caddy, Nginx or Apache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP extensions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mysqli&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;gd&lt;/code&gt; or &lt;code&gt;imagick&lt;/code&gt;, &lt;code&gt;intl&lt;/code&gt;, &lt;code&gt;mbstring&lt;/code&gt;, &lt;code&gt;xml&lt;/code&gt;, &lt;code&gt;zip&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Plus OPcache for performance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Option 1: install WordPress manually on Ubuntu 24.04
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Update the server and create a site user
&lt;/h3&gt;

&lt;p&gt;Each site should run as its own Linux user, so a compromised plugin on one site cannot read another site's files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--group&lt;/span&gt; &lt;span class="nt"&gt;--home&lt;/span&gt; /srv/blog blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install PHP 8.4 and WordPress's extensions
&lt;/h3&gt;

&lt;p&gt;Ubuntu 24.04 ships PHP 8.3. For PHP 8.4, add the Ondřej Surý PPA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;add-apt-repository ppa:ondrej/php &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;php8.4-fpm php8.4-mysql php8.4-curl php8.4-gd php8.4-intl &lt;span class="se"&gt;\&lt;/span&gt;
  php8.4-mbstring php8.4-xml php8.4-zip php8.4-imagick php8.4-opcache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give the site its own PHP-FPM pool, running as the site's user, in &lt;code&gt;/etc/php/8.4/fpm/pool.d/blog.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[blog]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="py"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;blog&lt;/span&gt;
&lt;span class="py"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;blog&lt;/span&gt;
&lt;span class="py"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/run/php/php8.4-fpm-blog.sock&lt;/span&gt;
&lt;span class="py"&gt;listen.owner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;
&lt;span class="py"&gt;listen.group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;
&lt;span class="py"&gt;pm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;dynamic&lt;/span&gt;
&lt;span class="py"&gt;pm.max_children&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;
&lt;span class="py"&gt;pm.start_servers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;
&lt;span class="py"&gt;pm.min_spare_servers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;pm.max_spare_servers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;
&lt;span class="na"&gt;php_admin_value&lt;/span&gt;&lt;span class="nn"&gt;[upload_max_filesize]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;64M&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;php_admin_value&lt;/span&gt;&lt;span class="nn"&gt;[post_max_size]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;64M&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;php_admin_value&lt;/span&gt;&lt;span class="nn"&gt;[memory_limit]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;256M&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;sudo systemctl reload php8.4-fpm&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create the database
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;mariadb-server
&lt;span class="nb"&gt;sudo &lt;/span&gt;mariadb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt; &lt;span class="nb"&gt;CHARACTER&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'blog'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'a-long-random-password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'blog'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;FLUSH&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Download WordPress
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /srv/blog
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; blog curl &lt;span class="nt"&gt;-fsSLO&lt;/span&gt; https://wordpress.org/wordpress-7.1.tar.gz
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; blog &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; wordpress-7.1.tar.gz &lt;span class="nt"&gt;--strip-components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; blog &lt;span class="nb"&gt;rm &lt;/span&gt;wordpress-7.1.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WordPress publishes checksums next to every release; compare the SHA-1 or MD5 of the archive with the one on wordpress.org before extracting it on a production server.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Write &lt;code&gt;wp-config.php&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Copy &lt;code&gt;wp-config-sample.php&lt;/code&gt; to &lt;code&gt;wp-config.php&lt;/code&gt;, fill in the database name, user, password and &lt;code&gt;DB_HOST&lt;/code&gt; of &lt;code&gt;127.0.0.1&lt;/code&gt;, and replace the eight &lt;code&gt;AUTH_KEY&lt;/code&gt; … &lt;code&gt;NONCE_SALT&lt;/code&gt; lines with fresh values from &lt;code&gt;https://api.wordpress.org/secret-key/1.1/salt/&lt;/code&gt;. Add two hardening lines above the "That's all, stop editing!" comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DISALLOW_FILE_EDIT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// no theme/plugin code editor in wp-admin&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WP_DEBUG'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make the file readable only by the site user and the web server: &lt;code&gt;chmod 640 wp-config.php&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Install Caddy and serve the site
&lt;/h3&gt;

&lt;p&gt;Install Caddy from its official repository (the version in Ubuntu's archive is older), then describe the site in &lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;blog.example.com {
    root * /srv/blog
    encode zstd gzip

    # Never serve the config file, and never run PHP someone managed to upload.
    @sensitive path /wp-config.php /readme.html /license.txt
    respond @sensitive 404
    @uploaded_php path_regexp (?i)^/wp-content/uploads/.*\.php$
    respond @uploaded_php 404

    php_fastcgi unix//run/php/php8.4-fpm-blog.sock
    file_server
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sudo systemctl reload caddy&lt;/code&gt;. As soon as &lt;code&gt;blog.example.com&lt;/code&gt; resolves to the server, Caddy obtains a Let's Encrypt certificate and redirects HTTP to HTTPS — no Certbot, no renewal cron.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Run the famous five-minute install
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;https://blog.example.com&lt;/code&gt; and complete WordPress's installer: site title, admin username (not &lt;code&gt;admin&lt;/code&gt;), a strong password and your email. That's it — WordPress is live.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Add an object cache
&lt;/h3&gt;

&lt;p&gt;Every WordPress page load runs dozens of database queries; a persistent object cache keeps their results in memory between requests. Install Redis or Valkey, bind it to localhost with a password, add the PHP extension and an object-cache plugin, and point the plugin at the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: install WordPress with Hanoo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hanoo.io/" rel="noopener noreferrer"&gt;Hanoo&lt;/a&gt; does all of the above on your own server, from one form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: connect an Ubuntu server
&lt;/h3&gt;

&lt;p&gt;Any provider works. WordPress needs Ubuntu 22.04, 24.04 or 26.04 LTS with at least 1 CPU core, 1 GB of RAM and 10 GB of disk. Run the install command from &lt;strong&gt;Connect server&lt;/strong&gt;; the agent connects out over mutually authenticated TLS, opens no inbound management port, and asks you to approve the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: create a WordPress application
&lt;/h3&gt;

&lt;p&gt;Choose &lt;strong&gt;Create application → WordPress&lt;/strong&gt;, name it, pick the server and a PHP version (&lt;strong&gt;8.4&lt;/strong&gt; is recommended; 8.3 and 8.5 are available). Hanoo then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;downloads the pinned official &lt;strong&gt;WordPress 7.1&lt;/strong&gt; release from wordpress.org and verifies its checksum;&lt;/li&gt;
&lt;li&gt;creates an isolated Linux user and a dedicated &lt;strong&gt;PHP-FPM pool&lt;/strong&gt; for the site;&lt;/li&gt;
&lt;li&gt;creates a private &lt;strong&gt;MariaDB&lt;/strong&gt; database and user;&lt;/li&gt;
&lt;li&gt;writes &lt;code&gt;wp-config.php&lt;/code&gt; with the database credentials and &lt;strong&gt;eight unique salts&lt;/strong&gt;, &lt;code&gt;DISALLOW_FILE_EDIT&lt;/code&gt; on and debug output off;&lt;/li&gt;
&lt;li&gt;keeps &lt;code&gt;wp-content/uploads&lt;/code&gt; in a shared directory that survives updates and clones;&lt;/li&gt;
&lt;li&gt;configures &lt;strong&gt;Caddy&lt;/strong&gt; to refuse &lt;code&gt;wp-config.php&lt;/code&gt;, &lt;code&gt;readme.html&lt;/code&gt;, &lt;code&gt;license.txt&lt;/code&gt; and any PHP file under &lt;code&gt;uploads/&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The site is immediately reachable on its temporary Hanoo address with HTTPS. Open it and finish WordPress's own five-minute installer to create your admin account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: add your domain
&lt;/h3&gt;

&lt;p&gt;Under the application's &lt;strong&gt;Domains&lt;/strong&gt; tab, add &lt;code&gt;blog.example.com&lt;/code&gt;. Hanoo checks that its DNS points at your server, issues the certificate and switches the site over with zero downtime. Then update &lt;strong&gt;Settings → General → WordPress Address and Site Address&lt;/strong&gt; in wp-admin to the new domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: optimize
&lt;/h3&gt;

&lt;p&gt;The application's &lt;strong&gt;Optimize WordPress&lt;/strong&gt; tab scans the site for common slowdowns and measures time to first byte. From there you can turn on a &lt;strong&gt;per-site Valkey object cache&lt;/strong&gt; in one click — Hanoo installs Valkey if the server does not have it yet — and enable compression and browser caching for static files. Every change is reversible from the same tab.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: staging and backups
&lt;/h3&gt;

&lt;p&gt;Need a staging copy? &lt;strong&gt;Clone&lt;/strong&gt; the application onto the same server or another one; the copy gets its own database. Backups and SSH/SFTP access per application are on the application's own tabs.&lt;/p&gt;

&lt;h2&gt;
  
  
  WordPress security checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One Linux user and PHP-FPM pool per site.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wp-config.php&lt;/code&gt; not web-readable; no PHP execution in &lt;code&gt;uploads/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DISALLOW_FILE_EDIT&lt;/code&gt; on, so a stolen admin session cannot edit PHP from wp-admin.&lt;/li&gt;
&lt;li&gt;Unique admin username, strong password and two-factor authentication via a plugin.&lt;/li&gt;
&lt;li&gt;Automatic minor-version updates left on; plugins and themes updated weekly.&lt;/li&gt;
&lt;li&gt;Database bound to &lt;code&gt;127.0.0.1&lt;/code&gt;, never exposed publicly.&lt;/li&gt;
&lt;li&gt;Regular off-server backups of the database and &lt;code&gt;wp-content&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Which PHP version is best for WordPress?
&lt;/h3&gt;

&lt;p&gt;PHP 8.4 for new sites. The WordPress Hosting Team recommends PHP 8.4 or 8.5 for WordPress 7.1; PHP 8.3 still works, and PHP 8.2 reaches end of life at the end of 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Caddy good for WordPress?
&lt;/h3&gt;

&lt;p&gt;Yes. Caddy serves PHP through PHP-FPM just like Nginx, and it obtains and renews HTTPS certificates automatically. WordPress's pretty permalinks work with &lt;code&gt;php_fastcgi&lt;/code&gt; without extra rewrite rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  MariaDB or MySQL for WordPress?
&lt;/h3&gt;

&lt;p&gt;Either. MariaDB 10.11 or newer and MySQL 8.4 are both recommended. On Ubuntu 24.04, &lt;code&gt;mariadb-server&lt;/code&gt; from the default archive is MariaDB 10.11, which is fine for WordPress.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much RAM does a WordPress server need?
&lt;/h3&gt;

&lt;p&gt;1 GB runs a small site comfortably. Plan for 2 GB or more if you run WooCommerce, page builders or an object cache alongside the database on the same server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run several WordPress sites on one server?
&lt;/h3&gt;

&lt;p&gt;Yes — give each its own Linux user, PHP-FPM pool, database and database user so they are isolated from each other. Hanoo does this automatically for every application.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is an object cache and do I need one?
&lt;/h3&gt;

&lt;p&gt;An object cache stores the results of repeated database queries in memory (Redis or Valkey) between requests. It noticeably speeds up wp-admin, WooCommerce and logged-in traffic, and is worth enabling on any site with dynamic content.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://hanoo.io/blog/install-wordpress-ubuntu-24-04-caddy" rel="noopener noreferrer"&gt;Hanoo blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>ubuntu</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Deploy Laravel 13 to Production on Your Own Ubuntu Server</title>
      <dc:creator>SᕼᗩᕼIᗪ KᗩᖇIᗰI</dc:creator>
      <pubDate>Sat, 31 Oct 2026 16:43:15 +0000</pubDate>
      <link>https://dev.to/shahid_karimi/how-to-deploy-laravel-13-to-production-on-your-own-ubuntu-server-l1o</link>
      <guid>https://dev.to/shahid_karimi/how-to-deploy-laravel-13-to-production-on-your-own-ubuntu-server-l1o</guid>
      <description>&lt;p&gt;Running &lt;code&gt;php artisan serve&lt;/code&gt; on your laptop is easy. Running a Laravel 13 application in production — with HTTPS, zero-downtime deploys, migrations that don't take the site down, queue workers that survive reboots and a scheduler that actually fires — is where most teams reach for a PaaS and its bill.&lt;/p&gt;

&lt;p&gt;You don't have to. This guide walks through a production-grade Laravel 13 deployment on a single &lt;strong&gt;Ubuntu 24.04&lt;/strong&gt; server you own: first the moving parts and how to wire them by hand, then the same result with Hanoo, where a &lt;code&gt;git push&lt;/code&gt; becomes a built, verified and reversible release.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; Laravel 13 requires &lt;strong&gt;PHP 8.3 or newer&lt;/strong&gt; (8.4 is a good default). A production setup needs PHP-FPM, a web server pointing at &lt;code&gt;public/&lt;/code&gt;, a database, Composer, Node.js for Vite builds, an atomic release layout, a queue worker under a process supervisor, the scheduler running every minute, and HTTPS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Laravel 13 needs in production
&lt;/h2&gt;

&lt;p&gt;Laravel 13 was released in March 2026 and raised the minimum PHP version to 8.3. A typical production stack looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;Recommended choice&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;8.4 (8.3–8.5 supported)&lt;/td&gt;
&lt;td&gt;Laravel 13's minimum is 8.3; 8.4 is the mature, fast default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web server&lt;/td&gt;
&lt;td&gt;Caddy or Nginx with PHP-FPM&lt;/td&gt;
&lt;td&gt;Serves &lt;code&gt;public/&lt;/code&gt;, hands PHP to FPM, terminates TLS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;MariaDB / MySQL or PostgreSQL&lt;/td&gt;
&lt;td&gt;Bound to &lt;code&gt;127.0.0.1&lt;/code&gt; on a single-server setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache, sessions, queues&lt;/td&gt;
&lt;td&gt;Valkey (Redis-compatible) or the database&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;file&lt;/code&gt; and &lt;code&gt;sync&lt;/code&gt; drivers do not scale past one request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assets&lt;/td&gt;
&lt;td&gt;Node.js 22 or 24 with npm, pnpm or Yarn&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;vite build&lt;/code&gt; produces the files in &lt;code&gt;public/build&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Processes&lt;/td&gt;
&lt;td&gt;Supervisor&lt;/td&gt;
&lt;td&gt;Keeps &lt;code&gt;queue:work&lt;/code&gt; running and restarts it on failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduler&lt;/td&gt;
&lt;td&gt;Cron, every minute&lt;/td&gt;
&lt;td&gt;Runs &lt;code&gt;schedule:run&lt;/code&gt;, which runs your scheduled tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The zero-downtime release layout
&lt;/h2&gt;

&lt;p&gt;The single most important production habit is to &lt;strong&gt;never build in the directory that is serving traffic&lt;/strong&gt;. Instead, every deploy gets its own directory, and a symlink decides which one is live:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/srv/app/
├── releases/
│   ├── 20261015-142210/
│   └── 20261016-091503/    ← new release, built here
├── shared/
│   ├── .env                ← linked into every release
│   └── storage/            ← logs, sessions, uploads: survive deploys
└── current -&amp;gt; releases/20261016-091503
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A deploy clones the commit into a new release directory, links &lt;code&gt;shared/.env&lt;/code&gt; and &lt;code&gt;shared/storage&lt;/code&gt; into it, installs dependencies, builds assets, runs migrations, and only then repoints &lt;code&gt;current&lt;/code&gt;. Switching a symlink is atomic, so visitors either get the old release or the new one — never a half-built mix. Rolling back is pointing &lt;code&gt;current&lt;/code&gt; at the previous release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: deploy Laravel 13 by hand
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install PHP 8.4, Composer and Node.js
&lt;/h3&gt;

&lt;p&gt;Ubuntu 24.04 ships PHP 8.3, which meets Laravel 13's minimum. For PHP 8.4, use the Ondřej Surý PPA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;add-apt-repository ppa:ondrej/php &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;php8.4-fpm php8.4-cli php8.4-mysql php8.4-mbstring php8.4-xml &lt;span class="se"&gt;\&lt;/span&gt;
  php8.4-curl php8.4-zip php8.4-bcmath php8.4-intl php8.4-redis php8.4-opcache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Composer 2 from getcomposer.org (verify the installer's checksum), and Node.js 22 or 24 from NodeSource or a version manager.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Create a deploy user and the database
&lt;/h3&gt;

&lt;p&gt;Run the application as its own unprivileged user — never as &lt;code&gt;root&lt;/code&gt; and never as &lt;code&gt;www-data&lt;/code&gt; shared with other sites:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--group&lt;/span&gt; &lt;span class="nt"&gt;--home&lt;/span&gt; /srv/app laravel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a dedicated database and user that can connect only from &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Point the web server at &lt;code&gt;current/public&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;With Nginx, the essential bits are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;app.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/srv/app/current/public&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.php?&lt;/span&gt;&lt;span class="nv"&gt;$query_string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.php$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="nc"&gt;snippets/fastcgi-php&lt;/span&gt;&lt;span class="s"&gt;.conf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="s"&gt;unix:/run/php/php8.4-fpm-laravel.sock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class="nv"&gt;$realpath_root$fastcgi_script_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;DOCUMENT_ROOT&lt;/span&gt; &lt;span class="nv"&gt;$realpath_root&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note &lt;code&gt;$realpath_root&lt;/code&gt;: it resolves the &lt;code&gt;current&lt;/code&gt; symlink, so OPcache and FPM see the new release immediately after the switch instead of serving stale paths. Give the site its own PHP-FPM pool running as the &lt;code&gt;laravel&lt;/code&gt; user, then add HTTPS with Certbot or switch to Caddy, which obtains certificates automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Write a deploy script
&lt;/h3&gt;

&lt;p&gt;The core of every Laravel deploy script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;RELEASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/srv/app/releases/&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d-%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;
git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 &lt;span class="nt"&gt;--branch&lt;/span&gt; main git@github.com:acme/shop.git &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RELEASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /srv/app/shared/.env &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RELEASE&lt;/span&gt;&lt;span class="s2"&gt;/.env"&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RELEASE&lt;/span&gt;&lt;span class="s2"&gt;/storage"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /srv/app/shared/storage &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RELEASE&lt;/span&gt;&lt;span class="s2"&gt;/storage"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RELEASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt; &lt;span class="nt"&gt;--prefer-dist&lt;/span&gt; &lt;span class="nt"&gt;--optimize-autoloader&lt;/span&gt; &lt;span class="nt"&gt;--no-interaction&lt;/span&gt;
npm ci &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build
php8.4 artisan migrate &lt;span class="nt"&gt;--force&lt;/span&gt;
php8.4 artisan optimize
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sfn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RELEASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; /srv/app/current
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload php8.4-fpm
php8.4 artisan queue:restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add release pruning, a health check after the switch, and a rollback path, and you have the beginnings of a deployment tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Queue workers with Supervisor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[program:laravel-queue]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="py"&gt;command&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;php8.4 /srv/app/current/artisan queue:work --sleep=3 --tries=3 --max-time=3600&lt;/span&gt;
&lt;span class="py"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/srv/app/current&lt;/span&gt;
&lt;span class="py"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;laravel&lt;/span&gt;
&lt;span class="py"&gt;numprocs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;
&lt;span class="py"&gt;process_name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%(program_name)s_%(process_num)02d&lt;/span&gt;
&lt;span class="py"&gt;autostart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;autorestart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;stopwaitsecs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3600&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--max-time&lt;/code&gt; makes each worker exit periodically so Supervisor starts a fresh one, which picks up new code and releases memory. &lt;code&gt;queue:restart&lt;/code&gt; in the deploy script tells running workers to finish their current job and exit.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. The scheduler
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* * * * * cd /srv/app/current &amp;amp;&amp;amp; php8.4 artisan schedule:run &amp;gt;&amp;gt; /dev/null 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install it in the &lt;code&gt;laravel&lt;/code&gt; user's crontab, not root's.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: deploy Laravel 13 with Hanoo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hanoo.io/" rel="noopener noreferrer"&gt;Hanoo&lt;/a&gt; runs the release flow above for you, on your own server, with every step visible in the dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: connect your server
&lt;/h3&gt;

&lt;p&gt;Run the one-time install command from &lt;strong&gt;Connect server&lt;/strong&gt; on a clean Ubuntu 22.04, 24.04 or 26.04 LTS server. The agent connects out over mutually authenticated TLS — no inbound management port, no root password shared — and you approve the server in the dashboard. Laravel applications need at least 1 CPU core and 2 GB of RAM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: create the application from your repository
&lt;/h3&gt;

&lt;p&gt;Choose &lt;strong&gt;Create application&lt;/strong&gt;, select &lt;strong&gt;Laravel&lt;/strong&gt; and point it at your repository on &lt;strong&gt;GitHub, GitLab or Bitbucket&lt;/strong&gt;. The server generates a read-only &lt;strong&gt;deploy key&lt;/strong&gt;; you add its public half to the repository, and the private half never leaves your server. Pick &lt;strong&gt;PHP 8.4&lt;/strong&gt; (8.3–8.5 are available) and tick the database option if you want a private MariaDB database.&lt;/p&gt;

&lt;p&gt;Hanoo creates an isolated Linux user and PHP-FPM pool for the application, and writes its &lt;code&gt;.env&lt;/code&gt; from your &lt;code&gt;.env.example&lt;/code&gt; — with &lt;code&gt;APP_ENV=production&lt;/code&gt;, &lt;code&gt;APP_DEBUG=false&lt;/code&gt;, &lt;code&gt;APP_URL&lt;/code&gt; set to the application's address, the database credentials filled in when you asked for a database, and a freshly generated &lt;code&gt;APP_KEY&lt;/code&gt;. That file lives in the application's shared directory and is linked into every release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: what happens on every deploy
&lt;/h3&gt;

&lt;p&gt;Each deploy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetches the selected commit into a &lt;strong&gt;new, immutable release&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Detects your toolchain from the repository — &lt;code&gt;composer.lock&lt;/code&gt; means Composer; &lt;code&gt;pnpm-lock.yaml&lt;/code&gt;, &lt;code&gt;package-lock.json&lt;/code&gt; or &lt;code&gt;yarn.lock&lt;/code&gt; picks the JavaScript package manager — and installs checksum-verified versions of Composer and Node.js (20, 22 or 24).&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;composer install&lt;/code&gt; and your asset build &lt;strong&gt;as the application's own user&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Links the shared &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;storage/&lt;/code&gt; into the release and validates it.&lt;/li&gt;
&lt;li&gt;Runs your &lt;strong&gt;deploy pipeline&lt;/strong&gt; — steps such as &lt;code&gt;php artisan migrate --force&lt;/code&gt;, cache warm-up or queue restarts, grouped into &lt;em&gt;build&lt;/em&gt;, &lt;em&gt;before activation&lt;/em&gt; and &lt;em&gt;after activation&lt;/em&gt; phases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Activates the release atomically&lt;/strong&gt;, reloads PHP-FPM, and checks the site responds. A failed health check reverts to the previous release automatically.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every step is timed and its output is shown, so a failed build tells you why without an SSH session. A failed build never touches the running site, and any retained release can be restored with &lt;strong&gt;one-click rollback&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: domain and HTTPS
&lt;/h3&gt;

&lt;p&gt;Add &lt;code&gt;app.example.com&lt;/code&gt; under the application's &lt;strong&gt;Domains&lt;/strong&gt; tab. Hanoo checks that DNS points at your server, issues a Let's Encrypt certificate and switches the site over with zero downtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: queues and the scheduler
&lt;/h3&gt;

&lt;p&gt;The default &lt;code&gt;.env&lt;/code&gt; uses &lt;code&gt;QUEUE_CONNECTION=sync&lt;/code&gt;, which runs jobs inside the web request. For real background processing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;strong&gt;Valkey&lt;/strong&gt; from the server's &lt;strong&gt;Manage Services&lt;/strong&gt; (it is Redis-compatible, so Laravel's &lt;code&gt;redis&lt;/code&gt; driver works unchanged), or use the &lt;code&gt;database&lt;/code&gt; queue driver. Then set &lt;code&gt;QUEUE_CONNECTION&lt;/code&gt; and, if you use Valkey, &lt;code&gt;CACHE_STORE&lt;/code&gt; and &lt;code&gt;SESSION_DRIVER&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt; over SSH as the application user.&lt;/li&gt;
&lt;li&gt;Add a &lt;strong&gt;Supervisor program&lt;/strong&gt; for the application running &lt;code&gt;php8.4 artisan queue:work --sleep=3 --tries=3 --max-time=3600&lt;/code&gt;. Programs run as the application's user in its current release; you can start, stop and restart them and read their logs from the dashboard.&lt;/li&gt;
&lt;li&gt;Add an entry under the application's &lt;strong&gt;Cron jobs&lt;/strong&gt; tab running &lt;code&gt;php8.4 artisan schedule:run&lt;/code&gt; every minute. You can run it on demand and see the latest result and output.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Production checklist for Laravel 13
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;APP_ENV=production&lt;/code&gt; and &lt;code&gt;APP_DEBUG=false&lt;/code&gt; — debug mode leaks environment variables in error pages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;php artisan optimize&lt;/code&gt; after each deploy to cache config, routes, views and events.&lt;/li&gt;
&lt;li&gt;OPcache enabled; with an atomic symlink layout, resolve paths with &lt;code&gt;realpath&lt;/code&gt; so new releases are picked up.&lt;/li&gt;
&lt;li&gt;Sessions, cache and queues on Valkey/Redis or the database — not &lt;code&gt;file&lt;/code&gt; once you have more than one worker.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;storage/&lt;/code&gt; and &lt;code&gt;.env&lt;/code&gt; outside the release directory, shared across releases.&lt;/li&gt;
&lt;li&gt;Migrations that are backwards compatible with the previous release, so a rollback does not break.&lt;/li&gt;
&lt;li&gt;Queue workers restarted on every deploy, with &lt;code&gt;--max-time&lt;/code&gt; as a safety net.&lt;/li&gt;
&lt;li&gt;Scheduler running every minute as the application user.&lt;/li&gt;
&lt;li&gt;Off-server backups of the database and &lt;code&gt;storage/app&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What PHP version does Laravel 13 require?
&lt;/h3&gt;

&lt;p&gt;Laravel 13 requires PHP 8.3 or newer and supports PHP 8.3, 8.4 and 8.5. PHP 8.4 is a sensible production default today.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I deploy Laravel without downtime?
&lt;/h3&gt;

&lt;p&gt;Build each release in its own directory, share &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;storage/&lt;/code&gt; between releases, run migrations before the switch, then atomically repoint a &lt;code&gt;current&lt;/code&gt; symlink at the new release and reload PHP-FPM. Hanoo does exactly this on every deploy and reverts automatically if the new release fails its health check.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need Laravel Forge or a PaaS to host Laravel?
&lt;/h3&gt;

&lt;p&gt;No. Any Ubuntu server with PHP-FPM, a web server, a database and Supervisor can run Laravel in production. Tools like Hanoo automate the setup and the release flow while the server and code stay entirely yours.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I run Laravel queue workers in production?
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;php artisan queue:work&lt;/code&gt; under a process supervisor such as Supervisor, as the application's user, with &lt;code&gt;--max-time&lt;/code&gt; so workers recycle, and call &lt;code&gt;php artisan queue:restart&lt;/code&gt; after each deploy so they load the new code.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I run the Laravel scheduler on a server?
&lt;/h3&gt;

&lt;p&gt;Add one cron entry that runs &lt;code&gt;php artisan schedule:run&lt;/code&gt; every minute from the application's current directory. Laravel decides which scheduled tasks are due on each run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Valkey instead of Redis with Laravel?
&lt;/h3&gt;

&lt;p&gt;Yes. Valkey is an open-source fork of Redis that speaks the same protocol, so Laravel's &lt;code&gt;redis&lt;/code&gt; cache, session and queue drivers work with it unchanged.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://hanoo.io/blog/deploy-laravel-13-production-ubuntu" rel="noopener noreferrer"&gt;Hanoo blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>devops</category>
      <category>ubuntu</category>
    </item>
  </channel>
</rss>
