How to check that a TLS certificate matches its private key
Before reloading, extract the public key from both the certificate and the private key, compare their SHA-256 hashes, and handle read errors separately. Tested with OpenSSL 3.0.13: a matching pair, a different pair, and a damaged file.

In this article
- Prerequisites
- First, make sure both files can be read
- Extract the same object from both files
- A script with pipefail makes comparison safer
- Results of the reproducible test
- Do not confuse the site certificate with the chain
- Limits of this check
- What to check before and after applying the change
- Acceptance criteria
Если новый TLS-сертификат и приватный ключ относятся к разным парам, веб-сервер не сможет использовать их как одну конфигурацию. До reload извлеките открытый ключ из сертификата и из приватного ключа, выведите оба в DER и сравните SHA-256. Совпали хеши — пара соответствует. Различаются — ничего не применяйте: сначала установите причину различий.
Совпадение DER-представлений открытого ключа подтверждает соответствие пары, но не имя сайта, срок действия, цепочку доверия или то, какой сертификат уже отдаёт сервер. Различие DER ещё не доказывает, что ключи разные: нужно проверить также способ представления параметров. Эта проверка не заменяет остальные этапы приёмки TLS.
Входные условия
Linux с OpenSSL 3.x и GNU Coreutils; команды проверены на OpenSSL 3.0.13.
Сертификат в PEM-файле cert.pem и приватный ключ в private-key.pem. Для DER или PKCS#12 нужны другие параметры чтения.
Права только на чтение нужных файлов. Не копируйте приватный ключ в общий каталог и не публикуйте его содержимое.
Проверка выполняется до изменения конфигурации и не требует reload, restart или сетевого обращения.
Сначала убедитесь, что оба файла читаются
Начните с сертификата. Команда показывает субъект, издателя и даты, но не меняет файл:
openssl x509 -in cert.pem -noout -subject -issuer -dates
Остановитесь, если OpenSSL сообщает, что не может прочитать сертификат. Сначала уточните формат, путь и права. Хешировать пустой вывод после ошибки нельзя.
Отдельно проверьте структуру приватного ключа:
openssl pkey -in private-key.pem -check -noout
Для зашифрованного ключа OpenSSL запросит пароль. Не добавляйте секрет прямо в команду ради удобства: он может попасть в историю оболочки, список процессов или журнал автоматизации.
Извлеките один и тот же объект с двух сторон
Команда openssl x509 умеет вывести открытый ключ сертификата, а openssl pkey — открытую часть приватного ключа. DER убирает различия текстового оформления PEM, после чего можно сравнивать два SHA-256. Но DER не унифицирует все способы записи параметров: например, один EC-ключ с именованной кривой и с явными параметрами может дать разные хеши. При различии сначала диагностируют пару и представление параметров; сертификат не перевыпускают вслепую.
Хеш открытого ключа из сертификата:
openssl x509 -in cert.pem -pubkey -noout \
| openssl pkey -pubin -outform DER \
| sha256sum
Хеш открытой части приватного ключа:
openssl pkey -in private-key.pem -pubout -outform DER \
| sha256sum
В проверенной паре обе команды вернули один и тот же SHA-256 и код 0. Для другого корректного ключа обе команды также завершились без ошибки, но хеши различались. Поэтому отдельный зелёный код команды не доказывает совпадение пары.
Наблюдение | Вывод | Действие |
|---|---|---|
Оба конвейера завершились с кодом 0, хеши одинаковы | Сертификат соответствует ключу | Перейти к проверке конфигурации сервиса |
Оба конвейера завершились с кодом 0, хеши различаются | Соответствие пары по DER не подтверждено | Не применять; проверить пару и представление параметров. Подбирать другой ключ или перевыпускать сертификат только после диагностики |
Хотя бы один конвейер завершился с ненулевым кодом | Файл не разобран или команда не выполнена | Исправить путь, формат, права или пароль; хеши не сравнивать |
Безопаснее сравнивать скриптом с pipefail
При ручном копировании легко не заметить ошибку первой команды конвейера. В учебном тесте без pipefail повреждённый сертификат дошёл до sha256sum как пустой ввод. Получился известный хеш пустых данных — внешне обычная строка, хотя сертификат не был прочитан.
Ниже минимальная проверка. Она возвращает 0 при совпадении SHA-256 от DER-представлений, 1 при их различии и 2, если сертификат или ключ не удалось разобрать:
set -o pipefail
cert_hash="$({
openssl x509 -in cert.pem -pubkey -noout |
openssl pkey -pubin -outform DER |
sha256sum |
awk '{print $1}'
})" || exit 2
key_hash="$({
openssl pkey -in private-key.pem -pubout -outform DER |
sha256sum |
awk '{print $1}'
})" || exit 2
if [ "$cert_hash" = "$key_hash" ]; then
printf 'MATCH sha256=%s\n' "$cert_hash"
exit 0
fi
printf 'MISMATCH cert_sha256=%s key_sha256=%s\n' \
"$cert_hash" "$key_hash"
exit 1
Запускайте такой фрагмент в оболочке, где поддерживается pipefail, например Bash. Для автоматизации сохраните только итог и коды возврата; содержимое приватного ключа в отчёт не нужно.
Что показал воспроизводимый тест
Стенд: OpenSSL 3.0.13, Bash и временный каталог. Я создал две отдельные учебные RSA-пары, выпустил для каждой самоподписанный сертификат на два дня и выполнил сценарий дважды. Рабочие сертификаты, ключи и домены не использовались.
Сценарий | Наблюдение | Код |
|---|---|---|
cert-a.pem + key-a.pem | MATCH; одинаковый SHA-256 | 0 |
cert-a.pem + key-b.pem | MISMATCH; два разных SHA-256 | 1 |
Повреждённый сертификат + key-a.pem | OpenSSL не прочитал сертификат; сравнение не выполнялось | 2 |
openssl pkey -check для key-a.pem | Key is valid | 0 |
Случайные ключи дают новые значения SHA-256 при каждом прогоне, поэтому важны равенство или различие и код возврата, а не конкретная строка хеша.

Не перепутайте сертификат сайта с цепочкой
Для сравнения нужен сертификат сайта, которому должен соответствовать приватный ключ. Если файл содержит несколько сертификатов, явно убедитесь, какой из них читает команда. Промежуточный сертификат относится к ключу удостоверяющего центра и закономерно даст другой результат.
Не переставляйте сертификаты в рабочем fullchain наугад. Порядок и способ подключения зависят от веб-сервера и формата конфигурации; сначала работайте с копией и проверяйте окончательный набор файлов штатной командой сервиса.
Границы проверки
Совпадение открытых ключей не подтверждает, что сертификат ещё действует.
Оно не проверяет, покрывает ли сертификат нужное DNS-имя.
Оно не строит цепочку до доверенного корневого центра.
Оно не доказывает, что Nginx, Apache или балансировщик уже использует именно эти файлы.
Ключ в HSM или внешнем хранилище может не читаться как обычный PEM-файл; используйте контракт провайдера ключа.
Ненулевой код нельзя исправлять ослаблением прав или копированием секрета в доступное место без разбора причины.

Что проверить перед и после применения
До изменения: совпадение пары, ожидаемые subject и даты, резервная копия текущей конфигурации и точные пути файлов.
Перед reload: штатный тест конфигурации конкретного сервиса и той же сборки, которая обслуживает сайт.
После применения: ответ нужного виртуального хоста по TLS, имя, даты и цепочка фактически отданного сертификата.
При нескольких узлах: проверить каждый путь доставки отдельно; один успешный ответ не описывает весь кластер.
Для Nginx сначала выполните проверку конфигурации до её применения. После изменения сравните не файл на диске, а сертификат реального соединения; для этого можно использовать проверку SSL на сайте.
Критерий готовности
Пара готова к следующему этапу, когда оба файла отдельно читаются, скрипт вернул MATCH с кодом 0, данные сертификата ожидаемы, конфигурация сервиса прошла собственный тест, а после контролируемого применения реальное соединение отдаёт новый сертификат. Если любой шаг не подтверждён, остановитесь на диагностике: reload не исправляет неверную пару.
If a new TLS certificate and private key belong to different pairs, the web server cannot use them together in one configuration. Before reloading, extract the public key from the certificate and from the private key, output both in DER format, and compare their SHA-256 hashes. Matching hashes mean the pair matches. If they differ, do not apply the change: determine the reason for the difference first.
Matching DER representations of the public key confirm that the pair matches, but do not verify the site name, validity period, trust chain, or which certificate the server is already presenting. Different DER representations do not yet prove that the keys differ: the parameter encoding must also be checked. This check does not replace the other stages of TLS acceptance testing.
Prerequisites
Linux with OpenSSL 3.x and GNU Coreutils; the commands were tested with OpenSSL 3.0.13.
A certificate in the PEM file cert.pem and a private key in private-key.pem. DER or PKCS#12 files require different input options.
Read access to the required files only. Do not copy the private key to a shared directory or publish its contents.
The check takes place before any configuration change and requires no reload, restart, or network request.
First, make sure both files can be read
Start with the certificate. This command displays the subject, issuer, and dates without changing the file:
openssl x509 -in cert.pem -noout -subject -issuer -dates
Stop if OpenSSL reports that it cannot read the certificate. Check the format, path, and permissions first. Do not hash empty output after an error.
Check the private key structure separately:
openssl pkey -in private-key.pem -check -noout
For an encrypted key, OpenSSL will prompt for a password. Do not put the secret directly in the command for convenience: it may end up in shell history, the process list, or an automation log.
Extract the same object from both files
The openssl x509 command can output the certificate’s public key, while openssl pkey outputs the public part of the private key. DER removes differences in PEM text formatting, allowing the two SHA-256 hashes to be compared. However, DER does not normalize every parameter encoding: for example, the same EC key encoded with a named curve and with explicit parameters can produce different hashes. If the hashes differ, investigate the pair and parameter encoding first; do not reissue the certificate blindly.
Hash of the public key extracted from the certificate:
openssl x509 -in cert.pem -pubkey -noout \
| openssl pkey -pubin -outform DER \
| sha256sum
Hash of the public part extracted from the private key:
openssl pkey -in private-key.pem -pubout -outform DER \
| sha256sum
For the matching pair tested, both commands returned the same SHA-256 hash and exit status 0. With a different valid key, both commands also completed without errors, but the hashes differed. A successful command exit status alone therefore does not prove that the pair matches.
Observation | Conclusion | Action |
|---|---|---|
Both pipelines exited with status 0 and the hashes match | The certificate matches the key | Proceed to the service configuration check |
Both pipelines exited with status 0 and the hashes differ | A match has not been confirmed by the DER comparison | Do not apply the change; check the pair and parameter encoding. Select another key or reissue the certificate only after investigating |
At least one pipeline exited with a nonzero status | A file could not be parsed or a command failed | Correct the path, format, permissions, or password; do not compare the hashes |
A script with pipefail makes comparison safer
When copying commands manually, it is easy to miss an error in the first command of a pipeline. In the training test without pipefail, the damaged certificate reached sha256sum as empty input. This produced the well-known hash of empty data: an apparently ordinary hash string, even though the certificate had not been read.
The minimal check below returns 0 when the SHA-256 hashes of the DER representations match, 1 when they differ, and 2 if the certificate or key cannot be parsed:
set -o pipefail
cert_hash="$({
openssl x509 -in cert.pem -pubkey -noout |
openssl pkey -pubin -outform DER |
sha256sum |
awk '{print $1}'
})" || exit 2
key_hash="$({
openssl pkey -in private-key.pem -pubout -outform DER |
sha256sum |
awk '{print $1}'
})" || exit 2
if [ "$cert_hash" = "$key_hash" ]; then
printf 'MATCH sha256=%s\n' "$cert_hash"
exit 0
fi
printf 'MISMATCH cert_sha256=%s key_sha256=%s\n' \
"$cert_hash" "$key_hash"
exit 1
Run this snippet in a shell that supports pipefail, such as Bash. For automation, retain only the result and exit statuses; the report does not need the contents of the private key.
Results of the reproducible test
Test environment: OpenSSL 3.0.13, Bash, and a temporary directory. I created two separate training RSA key pairs, issued a self-signed certificate valid for two days for each, and ran the scenario twice. No production certificates, keys, or domains were used.
Scenario | Observation | Exit status |
|---|---|---|
cert-a.pem + key-a.pem | MATCH; the same SHA-256 hash | 0 |
cert-a.pem + key-b.pem | MISMATCH; two different SHA-256 hashes | 1 |
Damaged certificate + key-a.pem | OpenSSL could not read the certificate; no comparison was performed | 2 |
openssl pkey -check for key-a.pem | Key is valid | 0 |
Randomly generated keys produce new SHA-256 values on every run, so the relevant findings are whether the hashes match or differ and the exit status, rather than a particular hash string.

Do not confuse the site certificate with the chain
For this comparison, use the site certificate that should match the private key. If a file contains several certificates, explicitly confirm which one the command reads. An intermediate certificate belongs to a certification authority key and will naturally produce a different result.
Do not rearrange certificates in a production fullchain file at random. The order and configuration method depend on the web server and configuration format. Work on a copy first and check the final set of files with the service’s standard validation command.
Limits of this check
Matching public keys do not confirm that the certificate is still valid.
This check does not verify whether the certificate covers the required DNS name.
It does not build a chain to a trusted root certification authority.
It does not prove that Nginx, Apache, or the load balancer is already using these exact files.
A key in an HSM or external store may not be readable as an ordinary PEM file; follow the key provider’s interface requirements.
Do not address a nonzero exit status by weakening permissions or copying a secret to an accessible location without investigating the cause.

What to check before and after applying the change
Before the change: confirm that the pair matches, check the expected subject and dates, back up the current configuration, and confirm the exact file paths.
Before reloading: run the standard configuration test for the specific service, using the same build that serves the site.
After applying the change: check the TLS response from the intended virtual host, including the name, dates, and chain of the certificate actually presented.
With multiple nodes: check each delivery path separately; one successful response does not describe the entire cluster.
For Nginx, first run a configuration check before applying the change. After the change, inspect the certificate presented by a real connection rather than the file on disk; the website SSL check can help with this.
Acceptance criteria
The pair is ready for the next stage when both files can be read individually, the script returns MATCH with exit status 0, the certificate details are as expected, the service configuration passes its own test, and a live connection presents the new certificate after a controlled change. If any step is unconfirmed, continue diagnosing: a reload cannot fix a mismatched pair.





Discussion 0
Share your experience and ask questions. Comments without links appear after editorial review.
No comments yet. Start the discussion.