What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Yes, PHP GD can generate a small self-hosted image CAPTCHA: create a random code with random_int(), store only a server-side hash and expiry, render the code as a PNG, and compare the submitted value on the server with hash_equals().
This is suitable for learning, low-risk applications, and controlled internal tools. It is not a complete modern bot-defense system. Determined attackers may use OCR, replay flaws, new sessions, or direct endpoint abuse, so combine it with rate limiting, server-side validation, monitoring, and—where appropriate—a managed bot-detection service.
What a PHP GD CAPTCHA does
A CAPTCHA is a challenge intended to distinguish people from automated software. Passing one only shows that someone or something solved that particular challenge; it does not prove identity, good intent, or trustworthiness.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →PHP’s GD extension creates and manipulates raster images. For a CAPTCHA, the workflow is:
#1 Best Overall
- Generate a random code.
- Store a derived value and expiration time on the server.
- Create a blank bitmap with
imagecreatetruecolor(). - Fill its background and add moderate visual noise.
- Draw the challenge characters.
- Stream the result as a PNG.
- Normalize and validate the submitted answer server-side.
Lines, dots, rotation, and distortion may affect some OCR systems, but they do not create a reliable security boundary. Excessive distortion often harms legitimate users more than it harms modern automation.
Prerequisites
- PHP running through the web server or PHP-FPM.
- The GD extension enabled in that same PHP runtime.
- PNG support for
imagepng(). - Optional FreeType support and a readable local TrueType font for
imagettftext().
PHP’s GD installation documentation explains the platform-specific details. Unix builds use GD configuration such as --enable-gd; Windows installations use php_gd.dll. Before PHP 8.0, Windows commonly used the name php_gd2.dll.
Do not copy an old universal command such as sudo apt-get install php5-gd. Package names depend on your operating system, distribution, and PHP version. Install the GD package matching the PHP runtime used by your application, restart the relevant service, and verify it.
Check GD from the command line
php -m | grep -i gd
php -i | grep -i gd
Check GD inside PHP
<?php
if (extension_loaded('gd')) {
echo 'GD is enabled';
} else {
echo 'GD is not enabled';
}
CLI PHP and the PHP used by Apache or FPM can load different php.ini files. If the command-line check succeeds but the browser reports Call to undefined function imagecreatetruecolor(), inspect a temporary phpinfo() page through the web server. Check its loaded configuration file, enable GD for that runtime, restart PHP-FPM or the web server, and remove the diagnostic page afterward.
How the example works
GET form
→ generate code
→ store hash + expiry in the session
→ render image
POST form
→ normalize submitted answer
→ check expiry
→ compare hashes
→ consume the challenge
→ accept or show a fresh challenge
The example uses two files:
captcha-demo/
├── index.php
└── captcha.php
The image endpoint generates and outputs the image. The form endpoint validates the answer. The expected plaintext code is never sent to the browser.
Generate and stream the CAPTCHA image
Create captcha.php:
<?php
declare(strict_types=1);
session_start();
$width = 220;
$height = 70;
$length = 6;
// Avoid characters that are easy to confuse visually.
$alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
$code = '';
for ($i = 0; $i < $length; $i++) {
$code .= $alphabet[random_int(0, strlen($alphabet) - 1)];
}
// Keep only a derived value and an expiry time in the session.
$_SESSION['captcha'] = [
'hash' => hash('sha256', $code),
'expires' => time() + 300,
];
// Used by the page as a cache-busting value.
$_SESSION['captcha_version'] = bin2hex(random_bytes(8));
$image = imagecreatetruecolor($width, $height);
if ($image === false) {
http_response_code(500);
exit('Unable to create CAPTCHA image.');
}
$background = imagecolorallocate($image, 245, 247, 250);
$text = imagecolorallocate($image, 25, 35, 50);
$noise = imagecolorallocate($image, 150, 160, 175);
$border = imagecolorallocate($image, 100, 110, 125);
imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $background);
imagerectangle($image, 0, 0, $width - 1, $height - 1, $border);
// Moderate noise: enough variation without making the code needlessly hard to read.
for ($i = 0; $i < 8; $i++) {
imageline(
$image,
random_int(0, $width - 1),
random_int(0, $height - 1),
random_int(0, $width - 1),
random_int(0, $height - 1),
$noise
);
}
for ($i = 0; $i < 180; $i++) {
imagesetpixel(
$image,
random_int(0, $width - 1),
random_int(0, $height - 1),
$noise
);
}
// Built-in GD fonts are portable but limited. Font 5 is the largest bitmap font.
$x = 20;
for ($i = 0; $i < $length; $i++) {
imagestring(
$image,
5,
$x,
random_int(20, 34),
$code[$i],
$text
);
$x += 30;
}
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
// Omitting the filename streams PNG bytes directly to the response.
imagepng($image);
imagedestroy($image);
random_int() provides cryptographically secure, uniformly selected integers and is preferable to rand() for challenge generation. imagepng() outputs the image directly when its filename argument is omitted.
The session stores a SHA-256 hash rather than the answer itself. Hashing does not solve every security problem, but it avoids retaining the original answer unnecessarily. The five-minute lifetime is an example: choose a duration appropriate to the form and users.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesRank #2
Display and validate the challenge
Create index.php:
<?php
declare(strict_types=1);
session_start();
$message = null;
$messageClass = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$answer = strtoupper(trim((string)($_POST['captcha'] ?? '')));
$captcha = $_SESSION['captcha'] ?? null;
$valid = false;
if (
is_array($captcha) &&
isset($captcha['hash'], $captcha['expires']) &&
is_string($captcha['hash']) &&
is_int($captcha['expires']) &&
time() <= $captcha['expires'] &&
strlen($answer) <= 32
) {
$valid = hash_equals(
$captcha['hash'],
hash('sha256', $answer)
);
}
// Consume the challenge whether the answer was right or wrong.
unset($_SESSION['captcha']);
if ($valid) {
$message = 'CAPTCHA accepted.';
$messageClass = 'success';
} else {
$message = 'Incorrect or expired CAPTCHA. Please try again.';
$messageClass = 'error';
}
$_SESSION['captcha_version'] = bin2hex(random_bytes(8));
}
$version = $_SESSION['captcha_version']
??= bin2hex(random_bytes(8));
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>PHP GD CAPTCHA Demo</title>
</head>
<body>
<h1>PHP GD CAPTCHA Demo</h1>
<?php if ($message !== null): ?>
<p class="<?= htmlspecialchars($messageClass, ENT_QUOTES, 'UTF-8') ?>">
<?= htmlspecialchars($message, ENT_QUOTES, 'UTF-8') ?>
</p>
<?php endif; ?>
<form method="post">
<p>
<img
src="captcha.php?v=<?= htmlspecialchars($version, ENT_QUOTES, 'UTF-8') ?>"
alt="Enter the six-character code shown in this image"
width="220"
height="70"
>
</p>
<label for="captcha">CAPTCHA code</label>
<input
id="captcha"
name="captcha"
type="text"
inputmode="text"
autocomplete="off"
maxlength="6"
required
>
<button type="submit">Continue</button>
</form>
</body>
</html>
The generated alphabet excludes commonly confused characters such as 0, O, 1, and I. The validation policy is case-insensitive because both the generated characters and submitted value are normalized to uppercase.
hash_equals() is the appropriate timing-safe comparison for the derived strings. PHP documents the known value as the first argument and the user-controlled value as the second. Timing attacks are not usually the main CAPTCHA risk, but this is still the correct comparison pattern.
Important validation and abuse controls
Expire and consume every challenge
Checking only a session value leaves its lifetime and replay behavior undefined. The example expires challenges after five minutes and unsets the challenge after every attempt. A failed attempt must receive a new challenge; otherwise an attacker can make unlimited guesses against one answer.
Rate-limit attempts
A session-only counter is a useful demonstration but weak for a public application because an attacker can create new sessions:
Recommended Free Tools
$_SESSION['captcha_attempts'] =
(int)($_SESSION['captcha_attempts'] ?? 0) + 1;
if ($_SESSION['captcha_attempts'] > 5) {
http_response_code(429);
exit('Too many attempts. Try again later.');
}
For public systems, rate-limit a combination of signals such as the account, IP address, endpoint, and—where appropriate—device or browser reputation. Also rate-limit the image endpoint itself. Each request forces image generation and can consume CPU and memory; OWASP’s denial-of-service guidance describes the broader resource-exhaustion risk.
Keep the answer out of the client
Never place the expected code in HTML comments, hidden fields, query strings, image filenames, JavaScript variables, or client-side validation. Server-side storage is preferable, but protect the session from fixation, leakage, and insecure cookie configuration as you would any other session.
Keep CAPTCHA separate from CSRF protection
A CAPTCHA does not replace a CSRF token, authentication, authorization, or server-side form validation. For a state-changing form, validate a CSRF token independently:
Rank #3
<input
type="hidden"
name="csrf_token"
value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8') ?>"
>
A valid CAPTCHA should not by itself authorize a login, account change, purchase, or other privileged action.
Why streaming is better than creating PNG files
Older implementations often save files with names such as image123456789.png and delete old PNGs before generating a new one. That creates unnecessary filesystem work and can introduce:
- Orphaned files and cleanup races.
- Guessable filenames.
- Public exposure of challenge images.
- Collisions in shared directories.
- Accidental deletion of unrelated images.
The older SitePoint example warns that its cleanup directory must be isolated. Treat broad glob('*.png') and unlink() cleanup as unsuitable for production. Streaming with imagepng($image) avoids the entire class of problem.
Prevent stale CAPTCHA images
There are two separate issues:
- Cache freshness: the browser displays an old image.
- Challenge security: the server accepts an expired or reused answer.
Cache-busting does not strengthen the challenge. Use both response headers in captcha.php and a changing query value in the HTML. A session counter or bin2hex(random_bytes(8)) is more reliable than time(), which changes only once per second.
Also ensure that image loading does not unintentionally replace the challenge. If every image request generates a new code, browser prefetching, JavaScript reloads, or multiple CAPTCHA images can invalidate the code the user sees.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Multiple tabs and a more robust challenge store
The two-file example deliberately keeps one challenge in one session slot. That is easy to understand but can be overwritten when a user opens multiple forms or tabs.
A more robust design assigns a challenge ID and stores records like this:
challenge_id → {
answer_hash,
expires_at,
attempt_count,
session_binding
}
The form submits the challenge ID, and the server retrieves the matching record from a database, cache, or other private server-side store. Bind it to the session or another appropriate context, expire it, and delete it after validation. This also makes concurrent requests easier to reason about.
PHP sessions can lock while one request is running. Keep the image endpoint short. After writing the challenge, session_write_close() can release the session lock if the rest of the request does not need to modify the session. Higher-traffic applications may prefer a dedicated short-lived challenge store.
Free tools Windows power users keep installed
One-click scans. No signup required.
Use a TrueType font when the bitmap font is too limited
imagestring() uses GD’s built-in bitmap fonts. They are portable and require no font file, but the largest built-in font is visually limited. With FreeType-enabled GD, use imagettftext() and a known local font:
$font = __DIR__ . '/fonts/DejaVuSans-Bold.ttf';
if (!is_readable($font)) {
throw new RuntimeException('Font is missing or unreadable.');
}
$x = 18;
for ($i = 0; $i < strlen($code); $i++) {
imagettftext(
$image,
28,
random_int(-12, 12),
$x,
random_int(45, 58),
$text,
$font,
$code[$i]
);
$x += 32;
}
Use imagettfbbox() when calculating positions dynamically, particularly if rotating characters or changing font sizes. A relative font path can fail because it is resolved from the current working directory rather than the script directory, so __DIR__ is safer.
If the function fails, check that the file exists, is readable by the PHP process, and that GD includes FreeType support. Keep characters inside the image bounds; the TrueType baseline is not the same as the top edge.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshooting
The image is blank or corrupted
- Make sure no warning, HTML, whitespace, or UTF-8 byte-order mark is emitted before the PNG bytes.
- Confirm the response is
image/png. - Check that
imagecreatetruecolor()succeeded. - Call
imagepng()only after all drawing is complete. - Inspect the web-server PHP error log rather than printing diagnostics from the image endpoint.
The image changes unexpectedly
Look for multiple image requests, browser prefetching, reload scripts, reverse-proxy behavior, or multiple CAPTCHA elements. A challenge ID and server-side record can prevent one request from overwriting another challenge.
The CAPTCHA works in one browser but not another
Inspect cookies, session configuration, cache headers, and whether the image request is using the same host and protocol as the form. A blocked or misconfigured session cookie can make the image and POST request use different sessions.
The code space is not the same as the security level
With 32 possible characters and six positions, the theoretical space is:
32^6 = 1,073,741,824
That assumes uniform selection and does not represent real-world security. OCR, unlimited attempts, session flaws, answer leakage, challenge reuse, or direct endpoint abuse can make the effective difficulty far lower.
Accessibility is a design requirement
An image-only CAPTCHA can exclude people with visual, cognitive, motor, or language-related disabilities. An alt attribute such as “CAPTCHA” identifies the image but does not provide an equivalent way to complete the task. WCAG guidance on non-text content requires meaningful alternatives or equivalent access to the information and functionality.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallPossible alternatives include an audio challenge, a carefully designed non-visual challenge, email verification, risk-based detection, or a managed service with accessibility support. Avoid making the visual code so distorted that legitimate users cannot reliably read it. Accessibility is not improved by simply adding more noise.
When self-hosted GD is appropriate
A PHP GD CAPTCHA is reasonable when the goal is educational, the application is internal or low-risk, third-party services are prohibited, or the challenge is only one small layer in a broader abuse-control strategy.
It is a poor sole defense for financial actions, large public registration systems, credential-stuffing defense, account-takeover prevention, or other high-value operations. CAPTCHA is not authentication, authorization, rate limiting, or fraud detection.
When a managed alternative is better
Managed bot-detection services remove much of the image-generation and maintenance burden and may offer better accessibility and risk assessment. Cloudflare positions Turnstile as a CAPTCHA alternative that often assesses visitors without showing a traditional CAPTCHA.
Its integration uses a public site key in the page and a private secret key on the server; the server sends the submitted token to Cloudflare for verification. See the official setup documentation.
Cloudflare’s plan documentation, observed August 16, 2026, lists a Free plan and an Enterprise plan marked “Contact Sales.” The documented limits and features can change, so check the current plan page before choosing it. The trade-offs are an external provider, JavaScript and network dependencies, provider privacy and availability considerations, and possible restrictions for offline, air-gapped, or strict data-residency environments.
Other managed CAPTCHA and bot-detection services are available, but current pricing and behavior should be checked in each provider’s official documentation.
Quick Recap
Deployment checklist
- GD is enabled in the web-server PHP runtime, not only CLI PHP.
- PNG output works.
random_int()andrandom_bytes()are used for challenge-related randomness.- The expected answer stays server-side.
- The challenge has an expiry time.
- The challenge is consumed after every validation attempt.
- Failed attempts and image generation are rate-limited.
- No image is written to a public directory.
- Cache-control headers and a reliable cache-busting value are present.
- User-controlled output is escaped.
- CSRF protection is implemented separately.
- An accessible alternative is available.
- The CAPTCHA is not treated as the sole anti-abuse control.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

