1. Develop PHP Contact Form on LEMP Stack
๐ฏ Goal
Learn to manually install and configure a LEMP stack (Linux, Nginx, MySQL, PHP) on Ubuntu and create a simple PHP contact form application with database storage.
๐ Prerequisites
Before beginning this tutorial, you should:
- Have an Ubuntu 24.04 VM running on Azure
- Have SSH access to your VM
- Understand basic Linux command line operations
- Be familiar with basic HTML and PHP concepts
๐ Learning Objectives
By the end of this tutorial, you will:
- Install Nginx web server to serve web pages
- Install MySQL database for data storage
- Install PHP-FPM for server-side processing
- Configure Nginx to work with PHP for dynamic content
- Create a PHP contact form that stores data in MySQL
- Understand the request-response cycle in web applications
๐ Why This Matters
In real-world applications, LEMP stack knowledge is crucial because:
- It’s a widely used open-source web development platform
- It provides the foundation for dynamic web applications
- It demonstrates separation between web server, application logic, and data storage
- It will be foundational for understanding automated deployments we’ll build later
๐ Step-by-Step Instructions
Step 1: Connect to Your Ubuntu VM
Use SSH to connect to your Ubuntu VM:
ssh azureuser@<VM_Public_IP>Replace
<VM_Public_IP>with your VM’s actual public IP address or FQDN.
๐ก Information
- SSH (Secure Shell): A secure protocol for remotely accessing and managing servers
- azureuser: The default username created during Azure VM setup
- Make sure port 22 (SSH) is open in your Network Security Group
Step 2: Update System and Install Nginx
Update the package list to ensure you have the latest information:
sudo apt updateInstall Nginx web server:
sudo apt install nginx -yVerify Nginx is running:
sudo systemctl status nginxTest the web server by visiting your VM’s public IP in a browser. You should see the default Nginx welcome page.
๐ก Information
- Nginx: High-performance web server and reverse proxy
- systemctl: Command to manage system services on Ubuntu
- The
-yflag automatically answers “yes” to installation prompts- Nginx automatically starts and enables itself after installation
โ ๏ธ Common Mistakes
- Forgetting to update package lists may result in installing outdated packages
- Not checking service status might leave you unaware if Nginx failed to start
Step 3: Install MySQL Database Server
Install MySQL server:
sudo apt install mysql-server -yCheck that MySQL is running:
sudo systemctl status mysqlSet up MySQL root password (for our simple educational setup):
sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'SecurePass123!';"Create a dedicated user for PHP applications:
sudo mysql -u root -pSecurePass123! -e "CREATE USER IF NOT EXISTS 'php_user'@'localhost' IDENTIFIED BY 'php123';" sudo mysql -u root -pSecurePass123! -e "GRANT ALL PRIVILEGES ON *.* TO 'php_user'@'localhost';" sudo mysql -u root -pSecurePass123! -e "FLUSH PRIVILEGES;"
๐ก Information
- MySQL: Popular open-source relational database management system
- mysql_native_password: Authentication method compatible with PHP
- php_user: Dedicated database user for PHP applications
- The password setup here is for educational purposes - production systems need stronger security
Step 4: Install PHP and Configure for Nginx
Install PHP and necessary extensions:
sudo apt install php-fpm php-mysql -yCheck that PHP-FPM is running:
sudo systemctl status php8.1-fpmConfigure Nginx to process PHP files by editing the default site configuration:
sudo nano /etc/nginx/sites-available/defaultReplace the existing content with this configuration:
/etc/nginx/sites-available/defaultserver { listen 80; root /var/www/html; index index.php index.html index.nginx-debian.html; location / { try_files $uri $uri/ =404; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; } }Test the Nginx configuration:
sudo nginx -tReload Nginx to apply the changes:
sudo systemctl reload nginx
๐ก Information
- PHP-FPM: FastCGI Process Manager that handles PHP requests efficiently
- php-mysql: PHP extension for MySQL database connectivity
- fastcgi_pass: Tells Nginx where to send PHP requests (to PHP-FPM socket)
- try_files: Nginx directive that checks for file existence before processing
Step 5: Create Database Setup Script
Navigate to the web directory:
cd /var/www/htmlCreate a database setup script:
sudo nano database_setup.phpAdd the following PHP code:
/var/www/html/database_setup.php<?php // Database configuration $host = 'localhost'; $username = 'php_user'; $password = 'php123'; $database = 'contact_db'; try { // Connect to MySQL without specifying database first $pdo = new PDO("mysql:host=$host", $username, $password); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Create database if it doesn't exist $pdo->exec("CREATE DATABASE IF NOT EXISTS $database"); // Connect to the specific database $pdo = new PDO("mysql:host=$host;dbname=$database", $username, $password); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Create contacts table if it doesn't exist $sql = "CREATE TABLE IF NOT EXISTS contacts ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, message TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )"; $pdo->exec($sql); } catch(PDOException $e) { die("Database setup failed: " . $e->getMessage()); } ?>
๐ก Information
- PDO: PHP Data Objects - a database access layer providing uniform interface
- ATTR_ERRMODE: Configures how PDO handles errors
- AUTO_INCREMENT: MySQL feature that automatically increments ID values
- TIMESTAMP: MySQL data type that stores date and time
- This script creates both database and table automatically on first run
Step 6: Create Landing Page
Create the main landing page:
sudo nano index.htmlAdd the following HTML content:
/var/www/html/index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Simple Contact App</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="container"> <h1>Welcome to Our Contact Application!</h1> <p>This is a simple LEMP stack demonstration with a PHP contact form.</p> <p>You can submit messages and view all submitted messages.</p> <div class="navigation"> <a href="contact_form.html" class="button">Submit a Message</a> <a href="on_get_messages.php" class="button">View All Messages</a> </div> </div> </body> </html>
Step 7: Create CSS Styling
Create a CSS file for styling:
sudo nano style.cssAdd the following CSS content:
/var/www/html/style.cssbody { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; background-color: #f5f5f5; } .container { background-color: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center; } h1 { color: #333; margin-bottom: 20px; } p { color: #666; font-size: 18px; line-height: 1.6; margin-bottom: 30px; } .navigation { margin-top: 30px; } .button { display: inline-block; background-color: #007bff; color: white; padding: 15px 30px; text-decoration: none; border-radius: 5px; margin: 10px; font-size: 16px; font-weight: bold; } .button:hover { background-color: #0056b3; } /* Form styling */ .form-container { max-width: 600px; margin: 0 auto; text-align: left; } label { display: block; margin-bottom: 5px; font-weight: bold; color: #555; } input[type="text"], input[type="email"], textarea { width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } .submit-button { background-color: #28a745; color: white; padding: 12px 24px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } .submit-button:hover { background-color: #218838; }
Step 8: Create Contact Form
Create the contact form HTML:
sudo nano contact_form.htmlAdd the following content:
/var/www/html/contact_form.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Contact Form</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="container"> <h1>Contact Us</h1> <div class="form-container"> <form action="on_post_contact.php" method="POST"> <label for="name">Full Name:</label> <input type="text" id="name" name="name" required> <label for="email">Email Address:</label> <input type="email" id="email" name="email" required> <label for="message">Message:</label> <textarea id="message" name="message" rows="5" required></textarea> <button type="submit" class="submit-button">Send Message</button> </form> </div> <div class="navigation"> <a href="index.html" class="button">โ Back to Home</a> <a href="on_get_messages.php" class="button">View Messages</a> </div> </div> </body> </html>
๐ก Information
- action=“on_post_contact.php”: Specifies which PHP file handles form submission
- method=“POST”: HTTP method for sending form data securely
- required: HTML5 attribute ensuring fields are filled before submission
- External CSS: Linked CSS file maintains consistent styling across pages
Step 9: Create Form Processing Script
Create the PHP script to handle form submissions:
sudo nano on_post_contact.phpAdd the following PHP code:
/var/www/html/on_post_contact.php<?php // Include database setup require_once 'database_setup.php'; // Check if form was submitted if ($_SERVER["REQUEST_METHOD"] == "POST") { // Sanitize input data $name = htmlspecialchars(trim($_POST['name'])); $email = htmlspecialchars(trim($_POST['email'])); $message = htmlspecialchars(trim($_POST['message'])); try { // Insert data into database $sql = "INSERT INTO contacts (name, email, message) VALUES (:name, :email, :message)"; $stmt = $pdo->prepare($sql); $stmt->bindParam(':name', $name); $stmt->bindParam(':email', $email); $stmt->bindParam(':message', $message); $stmt->execute(); $success = true; } catch(PDOException $e) { $error = "Error saving message: " . $e->getMessage(); } } else { // Redirect if accessed directly header("Location: contact_form.html"); exit(); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Message Sent</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="container"> <?php if (isset($success)): ?> <h1>Thank You!</h1> <p>Your message has been sent successfully.</p> <div class="form-container"> <p><strong>Name:</strong> <?php echo $name; ?></p> <p><strong>Email:</strong> <?php echo $email; ?></p> <p><strong>Message:</strong> <?php echo nl2br($message); ?></p> </div> <?php else: ?> <h1>Error</h1> <p><?php echo $error; ?></p> <?php endif; ?> <div class="navigation"> <a href="contact_form.html" class="button">โ Send Another Message</a> <a href="on_get_messages.php" class="button">View All Messages</a> <a href="index.html" class="button">Home</a> </div> </div> </body> </html>
Step 10: Create Messages Display Script
Create the script to display all messages:
sudo nano on_get_messages.phpAdd the following PHP code:
/var/www/html/on_get_messages.php<?php // Include database setup require_once 'database_setup.php'; try { // Fetch all messages from database $sql = "SELECT * FROM contacts ORDER BY created_at DESC"; $stmt = $pdo->query($sql); $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch(PDOException $e) { $error = "Error fetching messages: " . $e->getMessage(); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>All Messages</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="container"> <h1>All Contact Messages</h1> <?php if (isset($error)): ?> <p>Error: <?php echo $error; ?></p> <?php elseif (empty($messages)): ?> <p>No messages found. <a href="contact_form.html">Submit the first message!</a></p> <?php else: ?> <?php foreach ($messages as $msg): ?> <div class="form-container" style="margin-bottom: 20px; border-left: 4px solid #007bff;"> <h3><?php echo htmlspecialchars($msg['name']); ?></h3> <p><strong>Email:</strong> <?php echo htmlspecialchars($msg['email']); ?></p> <p><strong>Message:</strong> <?php echo nl2br(htmlspecialchars($msg['message'])); ?></p> <p><strong>Submitted:</strong> <?php echo $msg['created_at']; ?></p> </div> <?php endforeach; ?> <?php endif; ?> <div class="navigation"> <a href="contact_form.html" class="button">Submit New Message</a> <a href="index.html" class="button">โ Back to Home</a> </div> </div> </body> </html>
Step 11: Set Proper File Permissions
Set proper ownership for all web files:
sudo chown -R www-data:www-data /var/www/html/Set appropriate file permissions:
sudo chmod 644 /var/www/html/*.php sudo chmod 644 /var/www/html/*.html sudo chmod 644 /var/www/html/*.css
๐ก Information
- www-data: Default web server user in Ubuntu
- 644: Read/write for owner, read-only for group and others
- Proper permissions ensure web server can read files but maintain security
๐งช Final Tests
Test 1: Verify LEMP Stack Installation
- Open your browser and visit
http://<VM_Public_IP>/ - You should see the welcome page with proper styling
- Check that all navigation links work correctly
Test 2: Test Database Connectivity
- Visit
http://<VM_Public_IP>/on_get_messages.php - You should see “No messages found” initially
- This confirms the database connection and table creation worked
Test 3: Test Complete Form Workflow
- Navigate to
http://<VM_Public_IP>/contact_form.html - Fill out the form with test data:
- Name: “John Doe”
- Email: “john@example.com”
- Message: “This is a test message”
- Submit the form
- You should see a confirmation page with your submitted data
- Navigate to “View All Messages” to see your submission in the list
โ Expected Results
- The welcome page displays with proper styling and navigation
- Form submission shows confirmation with submitted data
- Messages list displays all submitted messages with timestamps
- Database automatically creates tables on first access
- All page navigation works correctly
๐ง Troubleshooting
If Nginx is not serving pages:
- Check service status:
sudo systemctl status nginx - Check configuration:
sudo nginx -t - Check error logs:
sudo tail -f /var/log/nginx/error.log
If PHP is not processing:
- Check PHP-FPM status:
sudo systemctl status php8.1-fpm - Verify Nginx PHP configuration
- Check PHP error logs:
sudo tail -f /var/log/php8.1-fpm.log
If database connection fails:
- Check MySQL status:
sudo systemctl status mysql - Verify user credentials:
mysql -u php_user -pphp123 - Check MySQL error logs:
sudo tail -f /var/log/mysql/error.log
๐ Further Reading
- What is a LEMP Stack? - DigitalOcean - Comprehensive overview of LEMP stack components
- LEMP Stack Explained - Linode - In-depth guide to LEMP architecture and benefits
- W3Schools PHP Tutorial - Interactive PHP learning with examples
- W3Schools MySQL Tutorial - Learn MySQL database fundamentals
- W3Schools HTML Forms - HTML form elements and attributes
- Nginx Official Documentation - Web server configuration reference
- PHP Manual - Complete PHP language reference
Done! ๐
Great job! You’ve successfully implemented a complete LEMP stack and learned how to create dynamic PHP applications with database integration! This knowledge will help you understand automated deployments and more complex web applications. ๐