{"id":1015,"date":"2026-11-06T03:00:00","date_gmt":"2026-11-06T03:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=1015"},"modified":"2026-11-06T03:00:00","modified_gmt":"2026-11-06T03:00:00","slug":"install-lamp-stack-centos-stream-10","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/06\/install-lamp-stack-centos-stream-10\/","title":{"rendered":"How to Install LAMP Stack on CentOS Stream 10"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Install LAMP stack on CentOS Stream 10 with Apache, MariaDB, and PHP-FPM in about 10\u201315 minutes. You will finish with a boot-persistent web server, a locally secured database, and a disposable PHP test that proves the full stack is processing requests.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>A fresh CentOS Stream 10 VPS with at least 1 GB of RAM and 10 GB of free disk space.<\/li>\n\n\n<li>SSH access as <code>root<\/code>, or a normal user with working <code>sudo<\/code> privileges.<\/li>\n\n\n<li>A public IPv4 address if the website must be reachable from the internet; use <code>SERVER_IP<\/code> in the examples.<\/li>\n\n\n<li>A domain such as <code>YOUR_DOMAIN<\/code> is optional for this base setup, but needed before you configure a trusted HTTPS certificate.<\/li>\n\n\n<li>If you need the server, choose a <a href=\"https:\/\/vpslake.com\/buy-vps\">VPSLake Linux VPS<\/a> with root access and CentOS Stream 10.<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Update CentOS Stream and install the stack<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Updating the package metadata first prevents an old cache from causing dependency errors, and the native AppStream packages keep the installation aligned with this release.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo dnf update -y\nsudo dnf install -y httpd firewalld mariadb-server mariadb php php-cli php-fpm php-mysqlnd php-gd php-mbstring php-opcache curl<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Start the services and enable them for future boots. PHP-FPM is the FastCGI process manager used by the EL10 family, so Apache can hand PHP requests to a separate, managed service.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl enable --now firewalld httpd mariadb php-fpm\nsudo systemctl is-active firewalld httpd mariadb php-fpm<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output is four <code>active<\/code> lines:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>active\nactive\nactive\nactive<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">CentOS Stream follows the Enterprise Linux 10 PHP-FPM model described in the <a href=\"https:\/\/docs.redhat.com\/en\/documentation\/red_hat_enterprise_linux\/10\/html\/installing_and_using_dynamic_programming_languages\/using-the-php-scripting-language\" target=\"_blank\" rel=\"noopener\">official PHP and Apache documentation<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Allow web traffic through firewalld<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The daemons can be healthy while the site remains unreachable because the host firewall still blocks incoming connections. Keep SSH open, then allow only the web services needed for a normal HTTP-to-HTTPS deployment.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo firewall-cmd --permanent --zone=public --add-service=ssh\nsudo firewall-cmd --permanent --zone=public --add-service=http\nsudo firewall-cmd --permanent --zone=public --add-service=https\nsudo firewall-cmd --reload\nsudo firewall-cmd --zone=public --list-services<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The final command should include <code>ssh<\/code>, <code>http<\/code>, and <code>https<\/code>, along with any services already enabled by your VPS image:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cockpit dhcpv6-client http https ssh<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Do not add the <code>mysql<\/code> service or TCP port 3306. A PHP application on this same VPS can use MariaDB locally, and keeping the database off the public network removes an unnecessary attack surface. If SSH uses a custom port, allow that port before reloading the firewall instead of relying on the <code>ssh<\/code> service definition.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Confirm Apache before adding PHP code<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A local configuration check separates Apache problems from firewall and PHP problems, so test the web server before changing application files.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apachectl configtest\ncurl -I http:\/\/127.0.0.1\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see a valid configuration followed by a successful response:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Syntax OK\nHTTP\/1.1 200 OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The response headers and date vary. From another computer, open <code>http:\/\/SERVER_IP\/<\/code>; the default Apache page should load. If the local request works but the browser cannot connect, return to Step 2 and check the VPS provider\u2019s separate network firewall.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Secure and verify MariaDB<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The MariaDB hardening script removes anonymous accounts and test data while restricting administrative access to the local machine.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mariadb-secure-installation<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Answer the prompts to remove anonymous users, disallow remote root login, remove the test database, and reload the privilege tables. On current MariaDB packages, the <code>root@localhost<\/code> account commonly uses Unix-socket authentication; accept that secure option instead of inventing a database root password unless your application specifically requires password-based administration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Test the local client and print the server version:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mariadb -e \"SELECT VERSION() AS mariadb_version;\"\nsudo mariadb -e \"SHOW DATABASES;\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The first query returns a <code>mariadb_version<\/code> value. The second should list system databases such as <code>information_schema<\/code>, <code>mysql<\/code>, and <code>performance_schema<\/code>. See the <a href=\"https:\/\/mariadb.com\/docs\/server\/clients-and-utilities\/deployment-tools\/mariadb-secure-installation\" target=\"_blank\" rel=\"noopener\">MariaDB secure-installation documentation<\/a> for the purpose of each hardening prompt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Test PHP through PHP-FPM<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The PHP packages provide the interpreter and MariaDB driver, while PHP-FPM executes <code>.php<\/code> requests without requiring the older embedded Apache PHP module.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check the installed release and the important database modules, then restart the two services so Apache loads the PHP configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>php -v\nphp -m | grep -E 'mysqli|mysqlnd|PDO|pdo_mysql'\nsudo systemctl restart php-fpm httpd<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The version is controlled by the enabled CentOS Stream 10 repositories. The module check should include <code>mysqli<\/code>, <code>mysqlnd<\/code>, <code>PDO<\/code>, and <code>pdo_mysql<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Create a harmless test page, request it locally, and remove it immediately after the check. This confirms PHP execution without publishing the detailed server settings shown by <code>phpinfo()<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '&lt;?php echo \"PHP is working\\\\n\"; ?&gt;' | sudo tee \/var\/www\/html\/stack-test.php &gt;\/dev\/null\ncurl -fsS http:\/\/127.0.0.1\/stack-test.php\nsudo rm -f \/var\/www\/html\/stack-test.php<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PHP is working<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verify the LAMP stack<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run the final checks together to prove that systemd, Apache, PHP-FPM, and MariaDB are all responding:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl is-active httpd php-fpm mariadb\ncurl -fsS http:\/\/127.0.0.1\/\nsudo mariadb -e \"SELECT VERSION() AS mariadb_version;\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The service check should return three <code>active<\/code> lines. <code>curl<\/code> should return the Apache welcome page or your own index content, and the SQL query should return a version value. Before deploying a login page or other sensitive application, point <code>YOUR_DOMAIN<\/code> to the VPS and configure HTTPS.<\/p>\n\n\n\n<aside class=\"vl-element vl-cta-banner\" aria-label=\"Launch your LAMP server\">\n\t<div class=\"vl-cta-banner__content\">\n\t\t<p class=\"vl-cta-banner__eyebrow\">CentOS Stream VPS<\/p>\n\t\t<h3 class=\"vl-cta-banner__title\">Launch your LAMP server<\/h3>\n\t\t<p class=\"vl-cta-banner__text\">Run Apache, MariaDB, and PHP on a Linux VPS with root access.<\/p>\n\t<\/div>\n\t<a class=\"vl-cta-banner__button\" href=\"\/buy-vps\" aria-label=\"View Linux VPS plans\">View Linux VPS plans<\/a>\n<\/aside>\n\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><code>Job for httpd.service failed<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Apache found a malformed configuration file, commonly a typo in a virtual-host or PHP handler file. Read the exact error, fix the named file, and validate again before restarting.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apachectl configtest\nsudo journalctl -u httpd -n 40 --no-pager\nsudo systemctl restart httpd<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><code>curl<\/code> works locally but the site times out remotely<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Apache is listening, but firewalld or the VPS provider\u2019s external firewall is blocking the request. Confirm the services and listening sockets, then allow HTTP and HTTPS without exposing MariaDB.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo firewall-cmd --zone=public --list-services\nsudo ss -ltnp | grep -E ':(80|443)\\b'\nsudo firewall-cmd --permanent --zone=public --add-service=http\nsudo firewall-cmd --permanent --zone=public --add-service=https\nsudo firewall-cmd --reload<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If port 80 is listening locally and the rules are present, allow TCP 80 and 443 in the VPS control panel\u2019s network firewall as well.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The browser downloads PHP or displays PHP source<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Apache is treating the file as static content because PHP-FPM is stopped or its handler was not loaded. Check the service and the PHP-specific Apache configuration, then restart both services.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl status php-fpm --no-pager\nsudo test -f \/etc\/httpd\/conf.d\/php.conf &amp;&amp; echo 'PHP-FPM Apache config found'\nsudo journalctl -u php-fpm -n 40 --no-pager\nsudo systemctl restart php-fpm httpd<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Do not solve this by disabling SELinux or installing an obsolete PHP module; fix the service or the configuration reported by the logs.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>Access denied for user 'root'@'localhost'<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This usually means MariaDB is using Unix-socket authentication, which is normal on a fresh installation. Run local administrative queries through <code>sudo mariadb<\/code> rather than forcing <code>-u root -p<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mariadb -e \"SELECT USER(), CURRENT_USER();\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For an application, create a separate database user with only the permissions it needs instead of changing the root account.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hardening<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Replace the default Apache page with a named virtual host for <code>YOUR_DOMAIN<\/code>, then install a trusted certificate and redirect HTTP to HTTPS.<\/li>\n\n\n<li>Keep MariaDB on localhost unless a separate application server needs access; if remote access is necessary, restrict it to a specific source address and use TLS.<\/li>\n\n\n<li>Keep website files readable rather than writable by everyone. For files copied into <code>\/var\/www\/html<\/code>, restore the default SELinux labels with <code>sudo restorecon -Rv \/var\/www\/html<\/code>.<\/li>\n\n\n<li>Review the complete firewall state with <code>sudo firewall-cmd --zone=public --list-all<\/code> after adding each service.<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">FAQ<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Is MariaDB a replacement for MySQL in a PHP application?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">MariaDB supports the MySQL client protocol and the common PHP database APIs used by many applications. Check the application\u2019s compatibility matrix before using a feature that depends on a specific MySQL release.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need to open port 3306 for PHP?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. When PHP and MariaDB run on one VPS, the application can connect through the local socket or loopback interface. Open 3306 only for a documented remote client, and restrict the source instead of allowing the whole internet.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Which PHP version does CentOS Stream 10 install?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The enabled Stream 10 repositories determine the available version and point releases. Run <code>php -v<\/code> after installation rather than hard-coding a version that may change during the release lifecycle.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can I use the default Apache page in production?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">It is useful for the first connectivity test, but it is not a finished website configuration. Replace it with a virtual host, application files, HTTPS, and an application-specific database user before launch.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Is MariaDB a replacement for MySQL in a PHP application?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"MariaDB supports the MySQL client protocol and the common PHP database APIs used by many applications. Check the application\u2019s compatibility matrix before using a feature that depends on a specific MySQL release.\"}}, {\"@type\": \"Question\", \"name\": \"Do I need to open port 3306 for PHP?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"No. When PHP and MariaDB run on one VPS, the application can connect through the local socket or loopback interface. Open 3306 only for a documented remote client, and restrict the source instead of allowing the whole internet.\"}}, {\"@type\": \"Question\", \"name\": \"Which PHP version does CentOS Stream 10 install?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"The enabled Stream 10 repositories determine the available version and point releases. Run `php -v` after installation rather than hard-coding a version that may change during the release lifecycle.\"}}, {\"@type\": \"Question\", \"name\": \"Can I use the default Apache page in production?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"It is useful for the first connectivity test, but it is not a finished website configuration. Replace it with a virtual host, application files, HTTPS, and an application-specific database user before launch.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install LAMP stack on CentOS Stream 10 with Apache, MariaDB, PHP-FPM, firewalld, SELinux-safe permissions, verification, and fixes on a fresh VPS.","protected":false},"author":1,"featured_media":1016,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"csco_singular_sidebar":"","csco_page_header_type":"","csco_page_load_nextpost":"","footnotes":""},"categories":[5,9],"tags":[71,110,117,73,72],"class_list":["post-1015","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-apache","tag-centos-stream-10","tag-lamp-stack","tag-mariadb","tag-php","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1015","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/comments?post=1015"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1015\/revisions"}],"predecessor-version":[{"id":3907,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1015\/revisions\/3907"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/1016"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=1015"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=1015"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=1015"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}