SSD Nodes Learn
নির্দেশিকা Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-07-24

Ubuntu 24.04 এ LAMP stack সেটআপ করার নিয়ম

Ubuntu 24.04 এ Apache, MariaDB এবং PHP 8.3 PHP-FPM সহ পূর্ণাঙ্গ LAMP stack তৈরি করুন। Certbot দিয়ে free HTTPS এবং name-based vhost সেটআপ করার সঠিক পদ্ধতি জানুন।

আপনি যা তৈরি করছেন

একটি LAMP stack হলো একটি Ubuntu 24.04 সার্ভারে চারটি অংশ: নিচে Linux, HTTP রিকোয়েস্টের জন্য Apache, ডেটা সংরক্ষণের জন্য MariaDB, এবং কোড চালানোর জন্য PHP 8.3। এই নির্দেশিকা শেষে আপনার একটি name-based virtual host থাকবে যা একটি রিয়েল application directory সার্ভ করবে, একটি ডেটাবেস থাকবে যেখানে শুধুমাত্র প্রয়োজনীয় পারমিশন সম্পন্ন (least-privilege) ইউজার থাকবে, PHP-FPM এর মাধ্যমে Apache-এর সাথে কানেক্ট করা থাকবে, এবং সবশেষে একটি free Let's Encrypt certificate থাকবে।

ইনস্টলেশন প্রক্রিয়াটি সম্পন্ন করতে চারটি apt কমান্ড প্রয়োজন। এই গাইডের বেশিরভাগ অংশ হলো এই উপাদানগুলোর মধ্যে সংযোগ স্থাপন করা এবং সেই ছোট ভুলগুলো চিহ্নিত করা যা একটি নতুন stack-এ blank page দেখানো, ব্রাউজারে source code ডাউনলোড হিসেবে পাঠানো, অথবা ডেটাবেসে প্রবেশ করতে বাধা দেওয়ার কারণ হিসেবে কাজ করে। এই প্রতিটি ভুলের একটি নির্দিষ্ট লক্ষণ রয়েছে এবং নিচে প্রতিটি ভুলের ক্ষেত্রে আপনি যে টেক্সটটি দেখতে পাবেন তা উল্লেখ করা হয়েছে।

Prerequisites এবং সম্ভাব্য সমস্যাসমূহ

ধরে নিন আপনার কাছে একটি নতুন Ubuntu 24.04 KVM VPS আছে যেখানে sudo user বা root অ্যাক্সেস এবং একটি public IPv4 address রয়েছে। 1 GB RAM-এ একটি minimal stack চালানো সম্ভব; তবে কোনো real database-backed application চালানোর আগে 2 GB RAM ব্যবহার করা উচিত। কারণ MariaDB-এর default buffers এবং কিছু PHP-FPM worker মিলে দ্রুতই প্রথম 1 GB ব্যবহার করে ফেলে।

Certbot সঠিকভাবে কাজ করার জন্য নিচের দুটি শর্ত পূরণ করা প্রয়োজন:

  1. আপনার একটি domain name থাকতে হবে যার A recordটি VPS-এর public IP-তে পয়েন্ট করা থাকবে। Let's Encrypt ওই name-টির মাধ্যমে HTTP ব্যবহার করে validation সম্পন্ন করে; শুধুমাত্র IP address দিয়ে কোনো certificate পাওয়া সম্ভব নয়।
  1. ইন্টারনেট থেকে 80 এবং 443 port দুটি অ্যাক্সেসযোগ্য হতে হবে। অনেক provider-এর ক্ষেত্রে এর মানে হলো, আপনাকে কন্ট্রোল প্যানেলের network firewall এবং সার্ভারের ufw—উভয় জায়গাতেই এই port গুলো ওপেন করতে হবে।

DNS পরিবর্তন প্রোপাগেট হতে এক ঘণ্টা পর্যন্ত সময় নিতে পারে, তাই প্রথমে A record সেট করে রাখুন যাতে আপনার প্রয়োজন অনুযায়ী এটি প্রস্তুত থাকে।

Step 1 - Apache install করুন এবং default page নিশ্চিত করুন

sudo apt update
sudo apt install -y apache2

apt আপনার জন্য service টি start এবং enable করে দেয়। এটি পরীক্ষা করুন:

systemctl status apache2

আপনার active (running) লেখা একটি line দেখতে হবে। এখন browser-এ http://YOUR_SERVER_IP/ ওপেন করুন। বড় "It works!" banner সহ Apache2 Ubuntu Default Page দেখা যাওয়া মানে আপনার কাজ সঠিক হয়েছে — এটি Apache সঠিকভাবে serve করছে তার প্রমাণ। ওই page টি /var/www/html/index.html পাওয়ায় পাওয়া যায় এবং এটি default virtual host 000-default.conf দ্বারা serve করা হয়। আপনি পরে এই দুটিই disable করে দেবেন; আপাতত এগুলো থাকাই স্বাভাবিক।

যদি page টি লোড না হয় কিন্তু systemctl অনুযায়ী process টি running অবস্থায় থাকে, তবে বুঝতে হবে firewall বাধা দিচ্ছে। পরবর্তী ধাপটি সেটিই।

Step 2 - Open the firewall for HTTP and HTTPS

The apache2 package registers three ufw application profiles. List them:

sudo ufw app list

You will see Apache, Apache Full, and Apache Secure. Apache is port 80 only, Apache Secure is 443 only, and Apache Full is both — that is the one you want, because you are adding TLS at the end.

sudo ufw allow OpenSSH
sudo ufw allow "Apache Full"
sudo ufw enable

Allow OpenSSH before you run ufw enable. ufw defaults to denying all incoming traffic, and enabling it without an SSH rule cuts your own connection the moment it activates — you keep the current session but can never reconnect. Confirm with sudo ufw status; you want OpenSSH, Apache Full, and their v6 equivalents all reading ALLOW.

Step 3 - MariaDB ইনস্টল করুন এবং এটি সুরক্ষিত করুন

sudo apt install -y mariadb-server
systemctl status mariadb

Ubuntu 24.04-এ MariaDB 10.11 থাকে, যা একটি long-term-support রিলিজ। তাই আপনার কোনো external repository প্রয়োজন নেই। সার্ভিসটি চালু থাকলে, এটি harden করুন:

sudo mysql_secure_installation

Enter কী চাপার চেয়ে প্রম্পটগুলো পড়া ভালো। যখন current root password চাওয়া হবে, তখন Enter চাপুন — বর্তমানে কোনো পাসওয়ার্ড নেই। যখন "Switch to unix_socket authentication?" জিজ্ঞাসা করা হবে, উত্তরটি কোনো পরিবর্তন আনবে না কারণ এই প্যাকেজে এটি ইতিমধ্যে enabled আছে, তাই n চাপুন। পরবর্তী অনুচ্ছেদে বর্ণিত কারণে "Change the root password?" এর ক্ষেত্রে n বলুন, তারপর বাকিগুলোর জন্য Y দিয়ে উত্তর দিন: anonymous users মুছে ফেলা, remote root login নিষিদ্ধ করা, test database মুছে ফেলা, এবং privilege tables reload করা।

এই অংশটি সবাইকে বিভ্রান্ত করে। Ubuntu-এর MariaDB-তে root database account পাসওয়ার্ডের পরিবর্তে unix_socket authentication ব্যবহার করে। এর মানে হলো, ডাটাবেস সেই operating-system ইউজারকে বিশ্বাস করে যার মাধ্যমে আপনি ইতিমধ্যে authenticate হয়েছেন। তাই root shell থেকে এটি কাজ করে:

sudo mysql

...এবং এটি আপনাকে কোনো পাসওয়ার্ড ছাড়াই একটি MariaDB [(none)]> প্রম্পটে নিয়ে যাবে। একই কমান্ড কোনো unprivileged user হিসেবে চালালে তা refuse করা হবে, যা এই পদ্ধতির মূল উদ্দেশ্য: ডাটাবেস root অ্যাক্সেস সিস্টেমের sudo এর সাথে যুক্ত, এবং এখানে চুরি করার, phish করার বা brute-force করার মতো কোনো পাসওয়ার্ড নেই। এটি পাসওয়ার্ডের চেয়ে বেশি secure, তাই এটি পরিবর্তন করবেন না। এর থেকে প্রাপ্ত নিয়মটি হলো: কোনো অ্যাপ্লিকেশনকে কখনোই root অ্যাকাউন্টের সাথে কানেক্ট করবেন না। প্রতিটি অ্যাপ্লিকেশনের জন্য আলাদা ইউজার তৈরি করুন (Step 7), কারণ TCP-এর মাধ্যমে username এবং password ব্যবহার করে কানেক্ট করা কোনো অ্যাপ socket auth ব্যবহার করতে পারে না, এবং আপনি চান প্রতিটি অ্যাপ তার নিজস্ব ডাটাবেসের মধ্যে সীমাবদ্ধ থাকুক।

Step 4 - PHP 8.3 এবং PHP-FPM ইনস্টল করুন

Ubuntu 24.04-এর ডিফল্ট PHP হলো 8.3। FPM প্রসেস ম্যানেজার এবং একটি সাধারণ অ্যাপ্লিকেশনের জন্য প্রয়োজনীয় এক্সটেনশনগুলো ইনস্টল করুন:

sudo apt install -y php8.3-fpm php8.3-mysql php8.3-cli \
  php8.3-curl php8.3-xml php8.3-mbstring php8.3-zip

তালিকার বাইরে থাকা বিষয়টির দিকে খেয়াল রাখুন: libapache2-mod-php। এই পুরনো প্যাকেজটি প্রতিটি Apache প্রসেসের ভেতরে একটি PHP interpreter যুক্ত করে দেয়। এটি সহজ পদ্ধতি, কিন্তু প্রতিটি worker প্রসেস স্ক্রিপ্ট বা স্ট্যাটিক ইমেজ—উভয় ক্ষেত্রেই একটি করে PHP কপি বহন করে। এদের লাইফসাইকেল একই থাকে এবং এটি শুধুমাত্র Apache-এর prefork MPM-এর সাথে কাজ করে—যা সবচেয়ে কম দক্ষ। এর পরিবর্তে PHP-FPM PHP-কে নিজস্ব প্রসেস পুল হিসেবে চালায় এবং Apache একটি socket-এর মাধ্যমে এর সাথে যোগাযোগ করে। ফলে Apache স্ট্যাটিক ফাইলের জন্য threaded event MPM ব্যবহার করতে পারে এবং শুধুমাত্র PHP রিকোয়েস্টগুলো পাঠিয়ে দেয়। এই পুলটি ওয়েব সার্ভার থেকে আলাদাভাবে টিউন করা যায় এবং পরবর্তীতে nginx ব্যবহার করলেও একই FPM সেটআপ কাজ করবে। ভালো কারণেই এটি বর্তমানে ডিফল্ট হিসেবে ব্যবহৃত হয়।

Apache, proxy_fcgi মডিউলের মাধ্যমে FPM-এর সাথে যোগাযোগ করে। এটি এনাবল করুন, FPM প্যাকেজটি যে কনফিগারেশন ফাইলটি দিয়েছে সেটি এনাবল করুন এবং রিস্টার্ট দিন:

sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2

a2enconf php8.3-fpm, /etc/apache2/conf-available/php8.3-fpm.conf সক্রিয় করে, যেখানে PHP ফাইলগুলোকে FPM socket-এ পাঠানোর নিয়মটি থাকে। এর মূল অংশটি যেকোনো .php ফাইল শনাক্ত করে এবং সেটিকে /run/php/php8.3-fpm.sock পাথে থাকা socket-এ পাঠিয়ে দেয়:

<FilesMatch ".+\.ph(ar|p|tml)$">
    SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>

এই ফাইলটি এডিট করার প্রয়োজন নেই; এটি ডিফল্টভাবে সঠিকভাবে থাকে। তবে socket পাথটি জানা থাকলে আপনি পরবর্তীতে "PHP downloads instead of running" এবং "Primary script unknown" এর মতো সমস্যাগুলো সমাধান করতে পারবেন—এই সমস্যাগুলো মূলত Apache এবং FPM-এর মধ্যে এই socket বা ফাইলটি নিয়ে মতপার্থক্য থেকে ঘটে।

ধাপ 5 - আপনার অ্যাপের জন্য একটি name-based virtual host

Name-based virtual hosting একটি IP দিয়ে অনেকগুলো সাইট চালানোর সুবিধা দেয়; Apache রিকোয়েস্টের Host: header দেখে সাইটটি নির্বাচন করে। ডিফল্ট /var/www/html থেকে দূরে অ্যাপের জন্য একটি ডিরেক্টরি তৈরি করুন:

sudo mkdir -p /var/www/testapp
sudo chown -R www-data:www-data /var/www/testapp
sudo chmod -R 755 /var/www/testapp

Ownership বা মালিকানা গুরুত্বপূর্ণ। Ubuntu-তে Apache এবং PHP-FPM উভয়ই www-data ইউজার হিসেবে চলে, তাই ওয়েব সার্ভার যে ফাইলগুলো পড়বে — এবং অ্যাপ যে ডিরেক্টরিগুলোতে লিখবে (যেমন: uploads folder) — সেগুলোর মালিকানা www-data এর হওয়া উচিত। আপনি যদি আপনার লগইন ইউজার দিয়ে ফাইল এডিট করতে চান, তবে একটি সাধারণ পদ্ধতি হলো ফাইলগুলোর মালিকানা নিজের কাছে রাখা এবং আপনার ইউজারকে www-data গ্রুপে যুক্ত করা; সাধারণ ব্যবহারের জন্য www-data:www-data সবচেয়ে সুবিধাজনক।

/etc/apache2/sites-available/testapp.conf পাথে virtual host তৈরি করুন:

<VirtualHost *:80>
    ServerName app.example.com
    DocumentRoot /var/www/testapp

    <Directory /var/www/testapp>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/testapp-error.log
    CustomLog ${APACHE_LOG_DIR}/testapp-access.log combined
</VirtualHost>

ServerName এ আপনার আসল ডোমেইনটি সেট করুন। Options -Indexes নিশ্চিত করে যে কোনো index ফাইল না থাকলে Apache ডিরেক্টরি লিস্ট দেখাবে না — অন্যথায় ভিজিটররা আপনার source tree ব্রাউজ করতে পারবে। AllowOverride All একটি .htaccess ফাইল কাজ করতে দেয়, যা বেশিরভাগ PHP অ্যাপ্লিকেশন pretty URLs-এর জন্য প্রয়োজন হয়; আপনার অ্যাপের যদি এটি প্রয়োজন না হয়, তবে সামান্য গতি বৃদ্ধির জন্য এটি None এ রাখতে পারেন। এই সাইটটি enable করুন, default সাইটটি disable করুন, কনফিগারেশন চেক করুন এবং reload করুন:

sudo a2ensite testapp
sudo a2dissite 000-default
sudo apache2ctl configtest
sudo systemctl reload apache2

apache2ctl configtest কমান্ডটি Syntax OK প্রিন্ট করবে। a2dissite 000-default লাইনটি মানুষ প্রায়ই ভুলে যায়, আর এর কারণেই পরে ডিফল্ট পেজটি প্রদর্শিত হতে থাকে — যা failures সেকশনে বিস্তারিত আলোচনা করা হয়েছে।

Step 6 - PHP চলছে কিনা তা যাচাই করুন, তারপর প্রমাণটি মুছে ফেলুন

app root-এ একটি এক লাইনের PHP ফাইল তৈরি করুন:

echo "<?php phpinfo();" | sudo tee /var/www/testapp/info.php

http://app.example.com/info.php ভিজিট করুন। সঠিক ফলাফল হিসেবে একটি লম্বা বেগুনি এবং ধূসর রঙের PHP Version 8.3.x টেবিল দেখতে পাবেন যেখানে আপনার loaded modules গুলো তালিকাভুক্ত থাকবে এবং Server API লাইনে FPM/FastCGI লেখা থাকবে। এই শেষ লাইনটি নিশ্চিত করে যে রিকোয়েস্টগুলো mod_php এর পরিবর্তে PHP-FPM এর মাধ্যমে যাচ্ছে।

এখন এটি সাথে সাথে মুছে ফেলুন:

sudo rm /var/www/testapp/info.php

phpinfo() আপনার সঠিক PHP version, প্রতিটি loaded extension, file paths এবং environment details প্রকাশ করে দেয় — যা সার্ভারে কোনো known hole আছে এমন version খোঁজার জন্য যে কেউ ব্যবহার করতে পারে। এটি একটি পরীক্ষা মাত্র, কোনো feature নয়। পেজটি দেখার সাথে সাথে এটি মুছে ফেলুন। যদি টেবিলের পরিবর্তে আপনার ব্রাউজার info.php download করার অপশন দেখায়, তবে PHP, Apache এর সাথে কানেক্ট করা নেই; অন্য কিছু করার আগে failures section দেখুন।

Step 7 - Create the app database and a least-privilege user

Open the database as the socket-authenticated root:

sudo mysql

Then create one database and one user scoped to exactly that database:

CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Three deliberate choices here. utf8mb4 is real four-byte UTF-8 — the old utf8 alias silently truncates emoji and some CJK characters, so always use utf8mb4. The grant is on appdb.*, not *.*: this user can touch its own database and nothing else, so a SQL-injection hole in the app cannot read every other site's tables. And 'appuser'@'localhost' restricts the account to connections originating on the box itself.

Test it as that user:

mysql -u appuser -p appdb

It asks for the password and drops you at a MariaDB [appdb]> prompt. Notice there is no -h flag — leave it off and the client connects over the local Unix socket, which is exactly what MariaDB counts as localhost. One gotcha worth knowing: to MySQL and MariaDB, localhost means the Unix socket and 127.0.0.1 means a TCP connection. On a stock Ubuntu 24.04 MariaDB the server still resolves a TCP connection from 127.0.0.1 back to localhost, so both match the account — but on servers with skip-name-resolve enabled (a common performance tweak, and the norm in many container images), the two are matched as different hosts, and an app that dials 127.0.0.1 is refused with ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' (using password: YES) even when the password is correct.

So point your application at host localhost, user appuser, database appdb — never at root. PHP's mysqli and PDO both switch to the Unix socket when the host is the literal string localhost, matching the account you just created. If a framework insists on a numeric TCP host, create the user to match how it actually connects — 'appuser'@'127.0.0.1', or @'%' (paired with a firewall rule) only if it must reach the database from another machine.

Step 8 - Certbot দিয়ে HTTPS যুক্ত করুন

Plain HTTP-এর মাধ্যমে login form সার্ভ করলে password clear text হিসেবে চলে যায়, এবং প্রতিটি আধুনিক browser এই page-টিকে "Not secure" হিসেবে চিহ্নিত করে। Certbot একটি মাত্র command দিয়ে এটি সমাধান করতে পারে। Apache plugin সহ এটি install করুন:

sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache

Certbot এখানে দুটি plugin ব্যবহার করে। apache authenticator আপনার running Apache-এর মাধ্যমে একটি challenge file সাময়িকভাবে সার্ভ করার মাধ্যমে প্রমাণ করে যে আপনি domain-টির মালিক। এরপর apache installer আপনার virtual host-টি rewrite করে এতে 443 block যুক্ত করে, এটিকে নতুন certificate-এর দিকে নির্দেশ করে এবং ডিফল্টভাবে সমস্ত HTTP traffic-কে HTTPS-এ redirect করে — Certbot 2.0 থেকে redirect সংক্রান্ত কোনো প্রশ্ন থাকে না; আপনি যদি plain HTTP সার্ভ করতে চান তবে --no-redirect ব্যবহার করুন। যেহেতু আপনি Step 5-এ একটি সঠিক ServerName সেট করেছেন, Certbot স্বয়ংক্রিয়ভাবে domain শনাক্ত করতে পারে। Certificate গুলো 90 দিন পর্যন্ত কার্যকর থাকে এবং এই package-টি একটি systemd timer install করে যা এগুলোকে renew করে; timer-টি sudo certbot renew --dry-run দিয়ে যাচাই করুন, যার শেষে Congratulations, all simulated renewals succeeded থাকা উচিত।

Challenge, renewal timer, এবং DNS ও firewall সংক্রান্ত প্রয়োজনীয়তার পূর্ণাঙ্গ নির্দেশনার জন্য issuing free Let's Encrypt TLS certificates with Certbot on Apache গাইডটি দেখুন।

Backups, upgrades, and hardening

আপনার সিস্টেমের স্টেট ধরে রাখার জন্য দুটি জিনিস ব্যাকআপ নিন: databases এবং web root। প্রতি রাতে একটি logical dump নেওয়া সবচেয়ে সহজ এবং নির্ভরযোগ্য পদ্ধতি — sudo sh -c 'mysqldump --all-databases --single-transaction | gzip > /root/db-$(date +%F).sql.gz', যা পরে সার্ভার থেকে কপি করে নিতে হবে। পুরো পাইপলাইনটি sudo sh -c দিয়ে র‍্যাপ করা জরুরি: এটি না থাকলে shell আপনার ইউজার হিসেবে > /root/... redirect চালাবে এবং Permission denied এর মাধ্যমে ফেইল করবে, কারণ শুধুমাত্র mysqldump sudo ইনহেরিট করেছে। --single-transaction টেবিলগুলোকে লক না করেই InnoDB টেবিলগুলোর একটি consistent snapshot প্রদান করে। এর সাথে /var/www এবং /etc/apache2/sites-available এর একটি tar যুক্ত করুন; তাহলে আপনি সেই ফাইলগুলো ব্যবহার করে একটি নতুন VPS-এ পুরো stack টি পুনরায় তৈরি করতে পারবেন।

Upgrades হলো একটি সাধারণ sudo apt update && sudo apt upgrade। সবচেয়ে বড় সমস্যা হলো PHP version bump — যখন ভবিষ্যতে Ubuntu ডিফল্ট হিসেবে PHP 8.4 ব্যবহার করবে, তখন apt হয়তো 8.3 এর পাশাপাশি php8.4-fpm ইনস্টল করবে, ফলে socket হয়ে যাবে /run/php/php8.4-fpm.sock, কিন্তু আপনার Apache config এখনো 8.3 socket নির্দেশ করবে। নতুন conf (sudo a2enconf php8.4-fpm) এনাবল করুন এবং পুরাতনটি ডিজেবল করুন, অন্যথায় একটি সাধারণ আপগ্রেডের পরেও আপনার সাইট Primary script unknown রিটার্ন করা শুরু করবে। যেহেতু PHP রিলিজগুলো LTS distro এর চেয়ে দ্রুত পরিবর্তন হয়, তাই কোনো নির্দিষ্ট patch version ফিক্স না করে বর্তমান PHP release notes চেক করুন।

প্রথম দিনেই দুটি hardening পদক্ষেপ নেওয়া উচিত। প্রথমত, সার্ভারে Fail2Ban watching SSH সেটআপ করুন — একটি পাবলিক VPS-এ কয়েক মিনিটের মধ্যেই অটোমেটেড লগইন অ্যাটেম্পট শুরু হয়, এবং একটি ছোট jail হাজার হাজার প্রচেষ্টাকে ব্যান করার আগে মাত্র কয়েকটি অ্যাটেম্পটে সীমাবদ্ধ করে দেয়। দ্বিতীয়ত, আপনি যদি ফাইল এডিট করার বদলে ব্রাউজারের মাধ্যমে Apache virtual hosts, MariaDB databases এবং ইউজার ম্যানেজ করতে চান, তবে the Webmin web-based control panel এই একই stack এর ওপর কাজ করে এবং আপনার লেখা কনফিগ ফাইলগুলোই নিয়ন্ত্রণ করে। এর কোনোটিই সিস্টেমের প্রতিটি অংশ সম্পর্কে জ্ঞান রাখার বিকল্প নয়, তবে উভয়ই দৈনন্দিন কাজের জটিলতা কমায়।

Failure modes, with the strings you will see

The default page will not go away. You edited your virtual host, reloaded, and the browser still shows "Apache2 Ubuntu Default Page" and its "It works!" banner. Apache serves the first matching virtual host, and when no ServerName matches the request, the alphabetically first config wins — 000-default.conf sorts before testapp.conf. Either the request's host name does not match your ServerName, or you never ran sudo a2dissite 000-default. Disable the default, sudo systemctl reload apache2, and confirm with apache2ctl -S, which prints the vhost map and shows which config owns the default. Clear the browser cache too; a cached 200 from the old page happily persists.

A .php file downloads instead of running. You open info.php and the browser downloads a file containing the raw <?php source, or shows it as plain text, instead of running it. Apache is serving the file as a static asset because the PHP handler is not attached — you skipped sudo a2enmod proxy_fcgi, or sudo a2enconf php8.3-fpm, or did not restart Apache afterwards. Run all three (Step 4) and reload. Confirm the module is loaded with apache2ctl -M | grep fcgi, which should list proxy_fcgi_module. This is a source-code leak, not a cosmetic bug, so fix it before putting anything real on the server.

ERROR 1698 (28000): Access denied for user 'root'@'localhost'. You ran mysql -u root or mariadb -u root without sudo. The root account uses unix_socket auth, so it only accepts you when your OS user is actually root. The fix is sudo mysql — no -u root, no password. This message is the expected behaviour of socket auth working correctly, not a broken install.

ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' from the application, with the right password. The account exists as 'appuser'@'localhost', but your app is connecting over TCP to 127.0.0.1 on a server where host-name resolution is disabled (skip-name-resolve), so MariaDB matches the two as different hosts — localhost is the Unix socket, 127.0.0.1 is TCP. Point the app at host localhost so it uses the socket and matches the account, or create a second account 'appuser'@'127.0.0.1' if the framework will only speak TCP.

AH01071: Got error 'Primary script unknown' in /var/log/apache2/testapp-error.log, with the browser showing File not found.. Apache handed the request to PHP-FPM, but FPM could not find the script at the path Apache gave it. Two usual causes: the FPM socket in your config points at a PHP version that is not installed (a php8.4 socket after an upgrade while only 8.3 runs), or the file genuinely is not there because DocumentRoot and the real directory disagree. Check the socket exists with ls -l /run/php/, confirm DocumentRoot matches where the file lives, and restart both php8.3-fpm and apache2.

AH00558: apache2: Could not reliably determine the server's fully qualified domain name on every restart. This is a harmless warning, not an error — Apache is telling you no global ServerName is set. Silence it by writing ServerName your.domain into /etc/apache2/conf-available/servername.conf and running sudo a2enconf servername.

(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80 when Apache starts. Another web server already holds port 80 — often a stray nginx from a previous experiment. Find it with sudo ss -ltnp | grep :80, then stop and disable the other service before starting Apache.

FAQ

mod_php or PHP-FPM - which should I use?

Use PHP-FPM. mod_php embeds an interpreter in every Apache process and forces the slow prefork MPM, so Apache carries PHP overhead even when serving a static image. PHP-FPM runs PHP as a separate, independently tuned pool that Apache reaches over a socket, works with the faster threaded event MPM, and moves unchanged to nginx later. It is the modern default; mod_php only makes sense for a legacy app that depends on some in-process behaviour.

Why does my browser download the PHP file instead of running it?

Apache is treating the .php file as a static download because no PHP handler is attached to it. On Ubuntu 24.04 with FPM that means you missed one of sudo a2enmod proxy_fcgi, sudo a2enconf php8.3-fpm, or the Apache restart afterwards. Run all three and reload, then verify with apache2ctl -M | grep fcgi that proxy_fcgi_module is listed. Until you fix it the server is leaking source code, so treat it as urgent.

Why is root access denied in MariaDB even with the right password?

Because there is no password — Ubuntu's MariaDB authenticates the root account by unix_socket, tying it to the operating-system root user. mysql -u root from a normal shell returns ERROR 1698 (28000): Access denied for user 'root'@'localhost' by design. Connect with sudo mysql instead, and create a separate password-authenticated user for any application rather than reusing root.

How do I add HTTPS to my LAMP site?

Install certbot and python3-certbot-apache, point a domain's A record at the server, then run sudo certbot --apache. The apache authenticator proves domain control through your running Apache and the installer rewrites the virtual host for port 443 and sets up automatic renewal. The full Certbot and Apache walk-through covers the challenge, the renewal timer, and the common failure modes.

#lamp#apache#mariadb#php-fpm#ubuntu