Captcha solving service blog | SolveCaptcha How to solve and bypass captcha for free

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_ocr package;
  • 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:

  1. Install Tesseract OCR.
  2. Install the PHP wrapper through Composer.
  3. Validate the input image.
  4. Convert it to grayscale.
  5. Improve contrast.
  6. Enlarge small text.
  7. Test an appropriate threshold.
  8. Restrict the recognized character set.
  9. Run Tesseract with a suitable page segmentation mode.
  10. Remove unwanted characters from the result.
  11. Validate the expected answer format.
  12. Delete temporary files.
  13. Test accuracy on a labeled image set.
  14. Limit retries and resource usage.
  15. 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.