How to solve and bypass captcha for free with Tesseract OCR
You can solve simple text-based captchas for free by running an OCR engine on your own computer or server.
One of the easiest options is Tesseract OCR, an open-source text-recognition engine. PHP applications can work with Tesseract through the thiagoalessio/tesseract_ocr wrapper.
This method is suitable for captchas that contain clear letters or numbers inside a static image. It can work well when the text has little distortion and the background is relatively clean.
It is not a universal captcha bypass. Modern systems such as reCAPTCHA, hCaptcha, Cloudflare Turnstile, GeeTest, and behavioral challenges cannot be solved with ordinary OCR alone.
The workflow in this guide is:
Load the captcha image
→ validate the file
→ preprocess the image
→ run Tesseract OCR
→ remove unwanted characters
→ validate the recognized answer
Use this method only on websites you own or are authorized to test and automate.
What Tesseract OCR can solve
Tesseract is designed to recognize text inside images.
It works best when the captcha contains:
- clear Latin letters;
- numbers;
- a plain background;
- consistent character spacing;
- limited rotation;
- little or no overlapping noise.
Examples of suitable captchas include:
AB12
K7M4P
92851
Recognition becomes less reliable when the image contains:
- heavily distorted characters;
- overlapping letters;
- random curves;
- complex backgrounds;
- multiple colors;
- severe rotation;
- very small text;
- characters drawn on Canvas;
- interactive tasks.
The quality of the input image is usually more important than the amount of PHP code used to process it.
Advantages of a local captcha solver
A local OCR solver has several practical benefits.
It is:
- free to run;
- available without an external API;
- suitable for offline processing;
- easy to integrate into PHP;
- fast for simple images;
- fully controlled by your application.
It also has important limitations.
You must maintain:
- image downloading;
- preprocessing;
- OCR configuration;
- validation;
- retry logic;
- model and language files.
Accuracy can vary significantly between websites because every captcha design is different.
Requirements
You need:
- PHP 8.1 or later;
- Composer;
- Tesseract OCR;
- the PHP GD extension;
- the
thiagoalessio/tesseract_ocrpackage; - a test captcha image.
Check the PHP version:
php --version
Check Composer:
composer --version
Check whether GD is enabled:
php -m | grep -i gd
On Windows PowerShell:
php -m | Select-String gd
Step 1: Install Tesseract OCR
Ubuntu and Debian
sudo apt update
sudo apt install tesseract-ocr -y
Confirm the installation:
tesseract --version
Arch Linux
sudo pacman -S tesseract
Fedora
sudo dnf install tesseract
macOS
Install Tesseract with Homebrew:
brew install tesseract
Windows
Download a Windows build from the links listed in the Tesseract documentation.
After installation, add the Tesseract directory to the system PATH.
A common installation path is:
C:\Program Files\Tesseract-OCR
Check the installation:
tesseract --version
If PHP cannot find the executable, specify the full path in the OCR configuration.
Step 2: Install the PHP wrapper
Create a new project directory:
mkdir free-captcha-solver
cd free-captcha-solver
Install the PHP package:
composer require thiagoalessio/tesseract_ocr
Composer creates:
vendor/
vendor/autoload.php
composer.json
composer.lock
Your script must load the Composer autoloader:
require __DIR__ . '/vendor/autoload.php';
Step 3: Create a minimal captcha solver
Save the following code as solve.php:
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use thiagoalessio\TesseractOCR\TesseractOCR;
$imagePath = __DIR__ . '/captcha.jpg';
if (!is_file($imagePath)) {
throw new RuntimeException(
"Captcha image was not found: {$imagePath}"
);
}
$ocr = new TesseractOCR($imagePath);
$rawText = $ocr
->lang('eng')
->psm(7)
->allowlist(
range('A', 'Z'),
range('0', '9')
)
->run();
$answer = preg_replace(
'/[^A-Z0-9]/',
'',
strtoupper($rawText)
);
if ($answer === null || $answer === '') {
throw new RuntimeException(
'Tesseract did not recognize a valid answer.'
);
}
echo "Recognized text: {$answer}" . PHP_EOL;
Run it:
php solve.php
Example output:
Recognized text: AB12
How the minimal script works
The script first verifies that the captcha image exists:
is_file($imagePath)
It then creates a Tesseract OCR instance:
$ocr = new TesseractOCR($imagePath);
The language is set to English:
->lang('eng')
Page segmentation mode 7 tells Tesseract to treat the image as one line of text:
->psm(7)
This mode is often suitable for short captcha strings.
The allowlist limits recognition to uppercase Latin letters and numbers:
->allowlist(
range('A', 'Z'),
range('0', '9')
)
Finally, the script removes spaces, punctuation, and other unwanted characters:
preg_replace(
'/[^A-Z0-9]/',
'',
strtoupper($rawText)
)
Step 4: Preprocess the captcha image
OCR accuracy often improves when the image is converted to grayscale, enlarged, and adjusted for stronger contrast.
PHP GD can perform basic preprocessing without additional external libraries.
The following function:
- loads JPEG, PNG, GIF, or WebP;
- converts the image to grayscale;
- increases contrast;
- enlarges the image;
- applies a simple threshold;
- saves the prepared image as PNG.
<?php
declare(strict_types=1);
function loadImage(string $path): GdImage
{
$imageInfo = getimagesize($path);
if ($imageInfo === false) {
throw new RuntimeException(
'The supplied file is not a valid image.'
);
}
$mimeType = $imageInfo['mime'] ?? '';
$image = match ($mimeType) {
'image/jpeg' => imagecreatefromjpeg($path),
'image/png' => imagecreatefrompng($path),
'image/gif' => imagecreatefromgif($path),
'image/webp' => imagecreatefromwebp($path),
default => false,
};
if (!$image instanceof GdImage) {
throw new RuntimeException(
"Unsupported image type: {$mimeType}"
);
}
return $image;
}
function preprocessCaptcha(
string $sourcePath,
string $outputPath
): void {
$source = loadImage($sourcePath);
imagefilter(
$source,
IMG_FILTER_GRAYSCALE
);
imagefilter(
$source,
IMG_FILTER_CONTRAST,
-45
);
$sourceWidth = imagesx($source);
$sourceHeight = imagesy($source);
$scale = 3;
$prepared = imagecreatetruecolor(
$sourceWidth * $scale,
$sourceHeight * $scale
);
if (!$prepared instanceof GdImage) {
imagedestroy($source);
throw new RuntimeException(
'Unable to create the prepared image.'
);
}
$white = imagecolorallocate(
$prepared,
255,
255,
255
);
imagefill(
$prepared,
0,
0,
$white
);
imagecopyresampled(
$prepared,
$source,
0,
0,
0,
0,
$sourceWidth * $scale,
$sourceHeight * $scale,
$sourceWidth,
$sourceHeight
);
$width = imagesx($prepared);
$height = imagesy($prepared);
$threshold = 165;
for ($y = 0; $y < $height; $y++) {
for ($x = 0; $x < $width; $x++) {
$rgb = imagecolorat(
$prepared,
$x,
$y
);
$red = ($rgb >> 16) & 0xFF;
$green = ($rgb >> 8) & 0xFF;
$blue = $rgb & 0xFF;
$brightness = (int) round(
($red + $green + $blue) / 3
);
$colorValue = $brightness < $threshold
? 0
: 255;
$color = imagecolorallocate(
$prepared,
$colorValue,
$colorValue,
$colorValue
);
imagesetpixel(
$prepared,
$x,
$y,
$color
);
}
}
if (!imagepng($prepared, $outputPath)) {
imagedestroy($source);
imagedestroy($prepared);
throw new RuntimeException(
'Unable to save the prepared image.'
);
}
imagedestroy($source);
imagedestroy($prepared);
}
The threshold value controls which pixels become black and which become white.
A lower threshold preserves only darker pixels. A higher threshold keeps more parts of the original image.
There is no universal threshold that works for every captcha design. Test values such as:
120
140
165
180
200
and compare the results.
Complete PHP solver with preprocessing
The following version combines validation, preprocessing, OCR, answer cleanup, and length checks.
Save it as captcha_solver.php:
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use thiagoalessio\TesseractOCR\TesseractOCR;
final class CaptchaSolver
{
private const SUPPORTED_MIME_TYPES = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
];
public function __construct(
private readonly string $tesseractPath = 'tesseract'
) {
}
public function solve(
string $sourcePath,
int $minimumLength = 4,
int $maximumLength = 6
): string {
$this->validateImage(
$sourcePath
);
$preparedPath = sys_get_temp_dir()
. DIRECTORY_SEPARATOR
. 'captcha_'
. bin2hex(random_bytes(8))
. '.png';
try {
$this->preprocess(
$sourcePath,
$preparedPath
);
$rawText = $this->recognize(
$preparedPath
);
$answer = $this->cleanAnswer(
$rawText
);
$this->validateAnswer(
$answer,
$minimumLength,
$maximumLength
);
return $answer;
} finally {
if (is_file($preparedPath)) {
unlink($preparedPath);
}
}
}
private function validateImage(
string $path
): void {
if (!is_file($path)) {
throw new RuntimeException(
"Image was not found: {$path}"
);
}
if (!is_readable($path)) {
throw new RuntimeException(
"Image is not readable: {$path}"
);
}
if (filesize($path) === 0) {
throw new RuntimeException(
'Captcha image is empty.'
);
}
$imageInfo = getimagesize($path);
if ($imageInfo === false) {
throw new RuntimeException(
'The file is not a valid image.'
);
}
$mimeType = $imageInfo['mime'] ?? '';
if (
!in_array(
$mimeType,
self::SUPPORTED_MIME_TYPES,
true
)
) {
throw new RuntimeException(
"Unsupported image type: {$mimeType}"
);
}
}
private function loadImage(
string $path
): GdImage {
$imageInfo = getimagesize($path);
if ($imageInfo === false) {
throw new RuntimeException(
'Unable to read image metadata.'
);
}
$mimeType = $imageInfo['mime'] ?? '';
$image = match ($mimeType) {
'image/jpeg' => imagecreatefromjpeg($path),
'image/png' => imagecreatefrompng($path),
'image/gif' => imagecreatefromgif($path),
'image/webp' => imagecreatefromwebp($path),
default => false,
};
if (!$image instanceof GdImage) {
throw new RuntimeException(
'Unable to load the captcha image.'
);
}
return $image;
}
private function preprocess(
string $sourcePath,
string $outputPath
): void {
$source = $this->loadImage(
$sourcePath
);
imagefilter(
$source,
IMG_FILTER_GRAYSCALE
);
imagefilter(
$source,
IMG_FILTER_CONTRAST,
-45
);
$sourceWidth = imagesx($source);
$sourceHeight = imagesy($source);
$scale = 3;
$prepared = imagecreatetruecolor(
$sourceWidth * $scale,
$sourceHeight * $scale
);
if (!$prepared instanceof GdImage) {
imagedestroy($source);
throw new RuntimeException(
'Unable to create a prepared image.'
);
}
$white = imagecolorallocate(
$prepared,
255,
255,
255
);
imagefill(
$prepared,
0,
0,
$white
);
imagecopyresampled(
$prepared,
$source,
0,
0,
0,
0,
$sourceWidth * $scale,
$sourceHeight * $scale,
$sourceWidth,
$sourceHeight
);
$this->applyThreshold(
$prepared,
165
);
if (!imagepng($prepared, $outputPath)) {
imagedestroy($source);
imagedestroy($prepared);
throw new RuntimeException(
'Unable to save the prepared image.'
);
}
imagedestroy($source);
imagedestroy($prepared);
}
private function applyThreshold(
GdImage $image,
int $threshold
): void {
$width = imagesx($image);
$height = imagesy($image);
$black = imagecolorallocate(
$image,
0,
0,
0
);
$white = imagecolorallocate(
$image,
255,
255,
255
);
for ($y = 0; $y < $height; $y++) {
for ($x = 0; $x < $width; $x++) {
$rgb = imagecolorat(
$image,
$x,
$y
);
$red = ($rgb >> 16) & 0xFF;
$green = ($rgb >> 8) & 0xFF;
$blue = $rgb & 0xFF;
$brightness = (int) round(
($red + $green + $blue) / 3
);
imagesetpixel(
$image,
$x,
$y,
$brightness < $threshold
? $black
: $white
);
}
}
}
private function recognize(
string $imagePath
): string {
$ocr = new TesseractOCR(
$imagePath
);
$ocr
->executable(
$this->tesseractPath
)
->lang('eng')
->psm(7)
->allowlist(
range('A', 'Z'),
range('0', '9')
);
return $ocr->run();
}
private function cleanAnswer(
string $rawText
): string {
$answer = preg_replace(
'/[^A-Z0-9]/',
'',
strtoupper($rawText)
);
if ($answer === null) {
throw new RuntimeException(
'Unable to clean the OCR result.'
);
}
return $answer;
}
private function validateAnswer(
string $answer,
int $minimumLength,
int $maximumLength
): void {
$length = strlen($answer);
if ($length < $minimumLength) {
throw new RuntimeException(
"The recognized answer is too short: {$answer}"
);
}
if ($length > $maximumLength) {
throw new RuntimeException(
"The recognized answer is too long: {$answer}"
);
}
}
}
$tesseractPath = PHP_OS_FAMILY === 'Windows'
? 'C:\\Program Files\\Tesseract-OCR\\tesseract.exe'
: 'tesseract';
$solver = new CaptchaSolver(
$tesseractPath
);
try {
$answer = $solver->solve(
__DIR__ . '/captcha.jpg',
minimumLength: 4,
maximumLength: 6
);
echo "Recognized text: {$answer}" . PHP_EOL;
} catch (Throwable $error) {
fwrite(
STDERR,
"Captcha recognition failed: "
. $error->getMessage()
. PHP_EOL
);
exit(1);
}
Run the solver:
php captcha_solver.php
Example result:
Recognized text: AB12
Generate a captcha image for testing
You can create a simple test image with ImageMagick.
Install ImageMagick on Ubuntu:
sudo apt install imagemagick -y
Generate a test image:
magick \
-size 180x70 \
xc:white \
-pointsize 34 \
-fill black \
-gravity center \
-annotate 0 "AB12" \
captcha.jpg
Older ImageMagick versions may use:
convert \
-size 180x70 \
xc:white \
-pointsize 34 \
-fill black \
-gravity center \
-annotate 0 "AB12" \
captcha.jpg
Run the PHP solver against the generated file.
This test verifies that:
- Tesseract is installed;
- PHP can start the executable;
- Composer autoloading works;
- the image is readable;
- the OCR wrapper is configured correctly.
It does not represent the difficulty of a real captcha with distortion and noise.
Choosing the correct page segmentation mode
Tesseract provides different page segmentation modes.
Useful modes for captcha recognition include:
| Mode | Description |
|---|---|
6 |
Treat the image as one uniform text block |
7 |
Treat the image as one text line |
8 |
Treat the image as one word |
10 |
Treat the image as one character |
13 |
Treat the image as one raw text line |
For most short text captchas, start with:
->psm(7)
For a captcha containing one compact word, test:
->psm(8)
For a single-character captcha, use:
->psm(10)
Run the same test set with different modes and compare the recognition rate.
Restricting recognized characters
A character allowlist can significantly improve accuracy.
For uppercase letters and numbers:
->allowlist(
range('A', 'Z'),
range('0', '9')
)
For numbers only:
->allowlist(
range('0', '9')
)
For lowercase letters:
->allowlist(
range('a', 'z')
)
Choose the narrowest valid character set.
Do not allow letters when the captcha contains only digits. Extra character options make incorrect recognition more likely.
Handling confusing characters
OCR engines commonly confuse visually similar characters.
Examples include:
0 and O
1 and I
1 and L
5 and S
8 and B
2 and Z
6 and G
When the captcha format is known, normalize only the combinations that are valid for that specific system.
For a numeric-only captcha:
$answer = strtr(
$answer,
[
'O' => '0',
'I' => '1',
'L' => '1',
'S' => '5',
'B' => '8',
]
);
Do not apply these substitutions to mixed alphanumeric captchas. The replacement may change a correctly recognized letter into an incorrect number.
Validating the answer
Never assume that every OCR result is valid.
Check:
- expected length;
- permitted characters;
- empty output;
- duplicate or malformed output;
- confidence across multiple preprocessing versions.
Example:
if (!preg_match('/^[A-Z0-9]{4,6}$/', $answer)) {
throw new RuntimeException(
'The OCR result does not match the expected format.'
);
}
For a four-digit captcha:
if (!preg_match('/^\d{4}$/', $answer)) {
throw new RuntimeException(
'Expected exactly four digits.'
);
}
Format validation does not prove that the answer is correct. It only rejects results that cannot be correct.
Trying several preprocessing variants
One preprocessing configuration will not work for every image.
A more reliable local solver can prepare several versions:
grayscale only
high contrast
threshold 140
threshold 165
threshold 190
inverted colors
2x resize
3x resize
Run OCR on each version and compare the answers.
When several variants return the same result, confidence is usually higher.
Example concept:
$answers = [
'AB12',
'AB12',
'A812',
'AB12',
];
$counts = array_count_values(
$answers
);
arsort($counts);
$bestAnswer = array_key_first(
$counts
);
This is still a heuristic. It does not guarantee that the most common answer is correct.
Common errors
tesseract command not found
Tesseract is not installed or cannot be found through the system PATH.
Check:
tesseract --version
On Windows, specify the full executable path:
->executable(
'C:\\Program Files\\Tesseract-OCR\\tesseract.exe'
)
Class TesseractOCR not found
The Composer dependency or autoloader is missing.
Run:
composer install
Confirm that the script contains:
require __DIR__ . '/vendor/autoload.php';
Call to undefined function imagecreatefromjpeg
The PHP GD extension is not installed or enabled.
Ubuntu:
sudo apt install php-gd
Restart PHP-FPM or the web server after installation.
Empty OCR result
Possible causes include:
- text is too small;
- contrast is too low;
- characters are heavily distorted;
- the wrong page segmentation mode is used;
- preprocessing removed parts of the characters;
- the file is not a real image.
Save the prepared image and inspect it manually.
Incorrect characters
Test:
- a different threshold;
- another page segmentation mode;
- a smaller allowlist;
- image enlargement;
- color inversion;
- a different Tesseract language model.
File not found
Use absolute paths based on:
__DIR__
Example:
$imagePath =
__DIR__ . '/captcha.jpg';
Relative paths may point to a different directory when the script runs through a web server or scheduler.
Permission denied
Confirm that the PHP process can:
- read the source image;
- write to the temporary directory;
- execute Tesseract;
- delete temporary files.
Security recommendations
Do not pass untrusted user input directly to executable paths or shell commands.
The PHP wrapper handles command execution, but your application must still validate:
- file paths;
- MIME types;
- file sizes;
- image dimensions;
- temporary filenames;
- accepted extensions.
Do not store uploaded files using their original filenames.
Generate random temporary names:
$filename =
bin2hex(random_bytes(16))
. '.png';
Limit image dimensions before preprocessing. Very large images can consume excessive memory and CPU.
Delete temporary files after recognition, including when an exception occurs.
Performance considerations
Tesseract starts a separate OCR process for each image.
This is usually acceptable for occasional captchas but can become expensive at higher volume.
To keep resource usage predictable:
- set a maximum image size;
- limit concurrent OCR processes;
- use timeouts;
- cache only non-sensitive preprocessing results;
- delete temporary files;
- monitor CPU and memory usage;
- reject malformed images before OCR.
Do not retry indefinitely when recognition fails.
Set a fixed attempt limit and return a clear error.
Limitations of free OCR captcha solving
Tesseract is designed for text recognition. It is not a complete modern captcha-solving system.
It may work for simple captchas containing:
- clear numbers;
- uppercase letters;
- one short text line;
- minimal distortion;
- a plain background.
It is generally unsuitable for:
- reCAPTCHA v2;
- reCAPTCHA v3;
- Invisible reCAPTCHA;
- reCAPTCHA Enterprise;
- hCaptcha;
- Cloudflare Turnstile;
- GeeTest;
- FunCaptcha;
- DataDome;
- ALTCHA;
- image-selection grids;
- object recognition;
- drag-and-drop challenges;
- behavioral verification;
- browser-fingerprint checks.
These systems require more than reading text from an image. They may validate tokens, browser state, JavaScript execution, cookies, interactions, IP addresses, or cryptographic challenges.
When to use SolveCaptcha
Use local Tesseract OCR when:
- the captcha is a static image;
- the answer is plain text;
- the image format is predictable;
- occasional recognition errors are acceptable;
- you want a fully local solution.
Use the SolveCaptcha captcha solver when:
- the captcha type changes;
- image distortion is too complex;
- a response token is required;
- browser state matters;
- the challenge is interactive;
- higher reliability is required;
- you need a documented API workflow.
SolveCaptcha supports integrations for multiple captcha types and programming languages.
Language-specific guides include:
Frequently asked questions
Can captcha be solved for free?
Simple image captchas can sometimes be solved for free with open-source OCR tools such as Tesseract.
Modern token-based or interactive captchas require a different approach.
Is Tesseract free?
Yes. Tesseract OCR is open-source and can run locally.
You still pay for your own server, electricity, maintenance, and development time.
Does Tesseract solve reCAPTCHA?
No. reCAPTCHA is not a simple text-recognition task.
Can Tesseract solve distorted text?
It can recognize lightly distorted text, especially after preprocessing.
Accuracy drops when characters overlap, rotate heavily, or blend into complex backgrounds.
Which PHP package should I use?
This guide uses:
thiagoalessio/tesseract_ocr
Install it with:
composer require thiagoalessio/tesseract_ocr
Which Tesseract mode works best?
Start with:
PSM 7
Then test modes 6, 8, and 13 using your own captcha dataset.
Should I resize the image?
Enlarging a small captcha by two or three times often improves recognition.
Avoid excessive enlargement because it can amplify noise.
Should I use grayscale?
Grayscale is a useful starting point.
Some captchas require color-based filtering before grayscale conversion.
How can I measure accuracy?
Create a labeled test dataset containing the original image and correct answer.
Run the solver against every image and calculate:
correct answers / total images × 100
Do not judge accuracy using only one or two examples.
Conclusion
A free PHP captcha solver can be built with Tesseract OCR when the challenge contains simple, readable text.
The correct workflow is:
- Install Tesseract OCR.
- Install the PHP wrapper through Composer.
- Validate the input image.
- Convert it to grayscale.
- Improve contrast.
- Enlarge small text.
- Test an appropriate threshold.
- Restrict the recognized character set.
- Run Tesseract with a suitable page segmentation mode.
- Remove unwanted characters from the result.
- Validate the expected answer format.
- Delete temporary files.
- Test accuracy on a labeled image set.
- Limit retries and resource usage.
- Use a captcha-solving API when OCR is not sufficient.
Tesseract is a practical free option for basic text captchas. It is not a replacement for a dedicated service when the challenge requires tokens, browser validation, behavioral analysis, or interactive recognition.