Your first PHP + MySQL site on shared hosting

A working guestbook - database, form, and safe output - in about an hour.

Shared hosting comes with PHP and MySQL ready to use, so you can go from nothing to a live, database-backed page without installing anything on a server. This guide builds a small guestbook: visitors leave a name and a message, and the page lists the latest entries. Along the way it covers the three security habits every PHP developer needs.

You'll need: a shared hosting plan with a domain, and access to its control panel (cPanel, hPanel, Plesk or similar). Menu names vary between hosts, but every step below has an equivalent.

1. Create the database and user

In your control panel, find the MySQL Databases section and:

  1. Create a database, e.g. guestbook. Many hosts add your account name as a prefix, giving something like u12345_guestbook - note the full name.
  2. Create a database user with a strong, generated password.
  3. Give that user access to the database (all privileges on that one database is fine for now).

The database host is usually localhost on shared hosting; your control panel will say if it's different.


2. Create the table

Open phpMyAdmin from the control panel, select your database, go to the SQL tab and run:

CREATE TABLE messages (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  body TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) DEFAULT CHARSET = utf8mb4;

utf8mb4 makes sure names and messages in any language - and emoji - are stored correctly.


3. Keep credentials out of the public folder

Your site's public files live in a folder such as public_html. Anything in it can potentially be requested by a browser, so put your database credentials one level above it, in a file called config.php:

<?php
// config.php - lives OUTSIDE public_html
return [
    'host' => 'localhost',
    'name' => 'u12345_guestbook',
    'user' => 'u12345_guestuser',
    'pass' => 'your-generated-password',
];

If your host doesn't let you write above public_html, put it in a subfolder and block web access to that folder - but most hosts allow it.


4. Connect with PDO

Create public_html/db.php:

<?php
$config = require __DIR__ . '/../config.php';

$pdo = new PDO(
    "mysql:host={$config['host']};dbname={$config['name']};charset=utf8mb4",
    $config['user'],
    $config['pass'],
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]
);

Exceptions mode means database errors stop the script instead of failing silently, and associative fetch mode gives you rows as ['name' => ..., 'body' => ...] arrays.


5. The guestbook page

Create public_html/index.php:

<?php
require __DIR__ . '/db.php';

$error = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = trim($_POST['name'] ?? '');
    $body = trim($_POST['body'] ?? '');

    if ($name === '' || $body === '') {
        $error = 'Please fill in both fields.';
    } elseif (mb_strlen($name) > 100 || mb_strlen($body) > 2000) {
        $error = 'That message is too long.';
    } else {
        $stmt = $pdo->prepare('INSERT INTO messages (name, body) VALUES (?, ?)');
        $stmt->execute([$name, $body]);
        header('Location: index.php');  // stop refresh re-posting
        exit;
    }
}

$messages = $pdo
    ->query('SELECT name, body, created_at FROM messages ORDER BY id DESC LIMIT 50')
    ->fetchAll();

function e(string $text): string {
    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}
?>
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Guestbook</title>
</head>
<body>
  <h1>Guestbook</h1>

  <?php if ($error): ?>
    <p><?= e($error) ?></p>
  <?php endif; ?>

  <form method="post">
    <label>Name <input name="name" maxlength="100" required></label>
    <label>Message <textarea name="body" maxlength="2000" required></textarea></label>
    <button>Sign the guestbook</button>
  </form>

  <?php foreach ($messages as $m): ?>
    <article>
      <h2><?= e($m['name']) ?></h2>
      <p><?= nl2br(e($m['body'])) ?></p>
      <small><?= e($m['created_at']) ?></small>
    </article>
  <?php endforeach; ?>
</body>
</html>

The three habits in that file

  • Prepared statements. The ? placeholders send user input to MySQL separately from the SQL itself, so no input can change what the query does. This one habit eliminates SQL injection.
  • Escape on output. Every value that came from a user goes through e(), which wraps htmlspecialchars(). Without it, someone could post a <script> tag that runs in every visitor's browser.
  • Post/Redirect/Get. After saving, the page redirects to itself, so refreshing doesn't submit the form twice.

6. Upload and test

Upload db.php and index.php to public_html and config.php to the folder above it, using the control panel's File Manager or an SFTP client such as FileZilla. Visit your domain and sign your own guestbook.

Then, in the control panel:

  • Enable SSL (most hosts provide a free certificate) and force HTTPS.
  • Choose the newest PHP version available.
  • Make sure errors aren't displayed to visitors - display_errors should be off in production; errors go to a log instead.

Next steps

  • Stop spam with a hidden "honeypot" field that real users leave empty - reject any submission that fills it in.
  • Add an admin page to delete messages, protected by a login using password_hash(), password_verify() and sessions.
  • Add CSRF protection to forms that change data: store a random token in the session and check it on every POST.
  • Enhance with JavaScript - see the Vanilla JS + PHP + MySQL pick for submitting forms with fetch.
All guides PHP primer