How to Host a PHP Website for Free (with a MySQL Database)
Where you can still host a PHP and MySQL website for free, a tested guestbook in index.php to put online today, the database host name trap that breaks most first uploads, and when it is time to move to paid hosting.
Yes, you can still host a PHP website with a MySQL database for free. Free hosting is good for learning, a portfolio, a school project, or trying a script before you pay for anything. It is not good for a business, and the free hosts change their rules often, so it helps to know the limits before you start.
This guide shows where free PHP hosting still works, gives you a small tested guestbook to put online, walks through the upload step by step, and explains the one setting that breaks most first attempts.
Free hosting that runs PHP and MySQL
| Option | What you get | The catch |
|---|---|---|
| InfinityFree | Shared hosting with 5 GB of disk, up to 400 MySQL databases, a recent PHP version, free SSL, a free subdomain or your own domain, no ads on your site | Daily limits on visits and server use, limits on file size and number of files, no PHP mail(), no SSH |
| Oracle Cloud Free Tier | A whole Linux server, free for good, where you install PHP and MySQL yourself | You manage everything, and free servers are often out of capacity. See our Oracle Cloud free VPS guide. |
| GitHub Pages, Netlify, Vercel | Free hosting for HTML, CSS and JavaScript | They do not run PHP or MySQL, so they only suit static sites |
| 000webhost | – | Closed in 2024. Old guides that recommend it are out of date. |
Numbers as listed by InfinityFree in October 2026. The rest of this guide uses InfinityFree, because it gives you cPanel-style hosting with MySQL without a credit card, but the code works on any PHP host.
Try it: a guestbook in index.php
A guestbook is the smallest real PHP and MySQL website: a form, a database table, and a list. Visitors leave a message and everyone sees it. It is also a good test of free hosting, because it shows whether PHP, the database and sessions all work. Save the code below as index.php; the next section shows where the four database values come from.
<?php
// index.php: a guestbook with PHP and MySQL, to test free hosting.
// Fill in the four database values from your hosting's MySQL page.
$db = new PDO(
'mysql:host=sql123.infinityfree.com;dbname=if0_12345678_guestbook;charset=utf8mb4',
'if0_12345678', // MySQL user name
'your-mysql-password', // MySQL password
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
$db->exec('CREATE TABLE IF NOT EXISTS messages (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(60) NOT NULL,
message VARCHAR(500) NOT NULL,
created DATETIME NOT NULL
)');
session_start();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(16));
}
function e($text) {
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$message = trim($_POST['message'] ?? '');
if (!hash_equals($_SESSION['token'], $_POST['token'] ?? '')) {
$error = 'Please reload the page and try again.';
} elseif (($_POST['website'] ?? '') !== '') {
$error = 'Spam check failed.'; // a hidden field that only bots fill in
} elseif ($name === '' || $message === '') {
$error = 'Please write your name and a message.';
} else {
$save = $db->prepare('INSERT INTO messages (name, message, created) VALUES (?, ?, NOW())');
$save->execute([mb_substr($name, 0, 60), mb_substr($message, 0, 500)]);
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?')); // a reload will not post twice
exit;
}
}
$messages = $db->query('SELECT name, message, created FROM messages ORDER BY id DESC LIMIT 50')->fetchAll();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Guestbook</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 30px auto; padding: 0 16px; }
input, textarea, button { width: 100%; box-sizing: border-box; padding: 10px; margin-top: 8px; font: inherit; }
.hide { display: none; }
.error { color: #b91c1c; }
.msg { border-bottom: 1px solid #ddd; padding: 12px 0; }
.msg small { color: #666; }
</style>
</head>
<body>
<h1>Guestbook</h1>
<form method="post">
<?php if ($error): ?><p class="error"><?= e($error) ?></p><?php endif; ?>
<input name="name" maxlength="60" placeholder="Your name" required>
<textarea name="message" maxlength="500" rows="4" placeholder="Your message" required></textarea>
<input class="hide" name="website" tabindex="-1" autocomplete="off">
<input type="hidden" name="token" value="<?= e($_SESSION['token']) ?>">
<button>Post message</button>
</form>
<?php foreach ($messages as $m): ?>
<div class="msg">
<strong><?= e($m['name']) ?></strong> <small><?= e($m['created']) ?></small>
<p><?= nl2br(e($m['message'])) ?></p>
</div>
<?php endforeach; ?>
</body>
</html>
| Part | What it does |
|---|---|
new PDO('mysql:host=...') | Connects to MySQL with the host, database, user and password from your hosting |
CREATE TABLE IF NOT EXISTS | Makes the messages table on the first visit, so there is no SQL to import |
prepare() and execute() | Prepared statements: what visitors type can never change your SQL |
e() with htmlspecialchars | Every message is escaped before it is shown, so a message containing <script> shows as text and never runs |
The token | A secret from the session in every form, so other websites cannot post to your guestbook |
The hidden website field | People never see it; spam bots fill in every field, and their posts are refused |
mb_substr | Cuts names to 60 and messages to 500 characters, even if someone skips the form's limits |
header('Location: ...') | After saving, the page reloads with a normal visit, so pressing refresh does not post the message twice |
Put it online on InfinityFree, step by step
- Create an account at InfinityFree and then a hosting account. Choose a free subdomain, or use a domain you own.
- Open the hosting account's Control Panel and then MySQL Databases. Create a database called
guestbook. - The MySQL page now shows four values: the MySQL host name (like
sql123.infinityfree.com), the database name (likeif0_12345678_guestbook), the user name (likeif0_12345678) and the password (your hosting account password, shown in the client area). Put all four into the top ofindex.php. - Open the File Manager (or connect with FileZilla using the FTP details from the client area) and go into the
htdocsfolder. That is your website's folder on InfinityFree, notpublic_html. - Delete the default welcome file there, upload
index.php, and open your subdomain in the browser. Post a message: if it appears, PHP and MySQL both work. - In the client area, request the free SSL certificate for your domain so the site opens with
https://.
The trap: the database host is not localhost
On most paid hosting the database host is localhost, and many tutorials and scripts take that for granted. On free hosting the database runs on a separate server, so localhost fails with SQLSTATE[HY000] [2002] or Connection refused. Always copy the host name from the MySQL page. The same goes for the prefix: the database is if0_12345678_guestbook, not guestbook.
Other first-upload errors are the same as on paid hosting, with fixes in our guide to uploading a website to cPanel.
What free hosting will not do
- Send email with PHP's
mail(). It is blocked to stop spam. Contact forms and password resets need an outside mail service through SMTP, for example with PHPMailer:
// Free hosts block PHP's mail(). Send through an outside mail service with PHPMailer instead:
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
$mail->isSMTP();
$mail->Host = 'smtp.your-mail-provider.com';
$mail->SMTPAuth = true;
$mail->Username = 'you@yourdomain.com';
$mail->Password = 'the-smtp-password';
$mail->SMTPSecure = 'tls';
$mail->Port = 587;
- Handle a traffic spike. When a site goes over the daily limits it is suspended until the next day, which is exactly when you have the most visitors.
- Run scheduled jobs or long scripts. There is no SSH, and long-running PHP scripts are stopped.
- Look professional. A free subdomain, no email at your own domain, and no promise of uptime are fine for a test, but not for customers.
When your site becomes real
Move to paid hosting when the site has regular visitors, takes payments or orders, or needs email. That is when these matter:
- Cheap paid shared hosting with cPanel, email at your domain, daily backups and a sensible renewal price. Our guide on cheap web hosting shows what to compare.
- A copy of the site to test changes on a subdomain, before the real site gets them
- Security that a guestbook does not need: admin logins with locking after wrong passwords, safe file uploads, and spam protection on every form
- A VPS only when you run software shared hosting does not allow, such as a chat or VPN server
Moving is easy: the guestbook, or any PHP site, runs on paid hosting unchanged once you put in the new database values.
A full PHP website to start with
If your goal is a real website rather than a test, our PHP blog script with admin panel is a complete, fast blog with no WordPress and no plugins:
- Articles with a cover picture, reading time, a table of contents and an FAQ section that Google reads as structured data
- Categories with sub-categories, live search, and editable About, Contact, Privacy and Terms pages
- SEO built in: canonical links, Open Graph, a sitemap and
llms.txt, plus AdSense places - A web installer, and admin security with a login lockout after 4 wrong passwords
It needs PHP 8.1 or newer and MySQL, which every normal paid host has. We also have a digital products shop, an online tools website and an HTML5 game portal that install the same way.
Where to go next
- How to upload a website to cPanel, for when you move to paid hosting
- Make a blog website in PHP with an admin panel
- Oracle Cloud free tier VPS, a free server for bigger projects
- What actually matters in cheap web hosting