Rotate with an overlap, not a swap: create a second key, deploy it to every service that reads the old one, watch the old key’s last-used time go quiet, and only then revoke it. That is how to rotate API keys safely on a live app, in 6 steps. Where a provider allows only one active key, write the rollback down before you touch anything.
What a key rotation runbook is
A key rotation runbook is one table with a row per credential and 8 columns: the credential, the owning account, every place it is read, what breaks if it changes, whether two keys can be live at once, the check, the rollback, and the last rotation date.
The runbook belongs with the other controls in secrets management best practices, and it is filed with the other operating runbooks, the way you would file them in a runbook template for a small SaaS. An urgent rotation becomes harder when nobody knows which services depend on a key.
Here is one row, filled in for a payment provider’s secret key. It is an illustration of the shape, not a record from any real app.
| Column | What goes in it | Illustrative row: a payment provider’s secret key |
|---|---|---|
| Credential and provider | The key’s name as the provider shows it, and whether it is test or live | Live secret key, payment provider |
| Owner account | The account the key was created in | The company’s own payment account, not a contractor’s |
| Every place it is read | Hosts, CI, edge functions, cron jobs, a teammate’s laptop | Web app host, the nightly billing job, the CI deploy step |
| What breaks if it changes | The services that fail when the value changes | Checkout, refunds, the billing job |
| Two live keys at once? | Whether the provider lets the old and new key work together, and for how long | Yes: the dashboard’s rotate option keeps both working for up to 7 days |
| The check | One real action that proves the new key works | A real request from each dependent succeeds, and the old key’s request log reaches zero |
| The rollback | How to go back if the new key fails | Put the old value back while it is still live, redeploy the same dependents |
| Last rotated | The date of the last completed rotation | Filled in at step 6 |
You should rotate API keys on triggers, not only on a calendar. My working rule has three: a person with access leaves, a key may have been exposed, and a fixed interval the team will actually keep. Stripe’s docs name the first one: rotate keys “when team members with access to the keys leave your organization.”
How often should API keys be rotated when none of those triggers fire? NIST’s key management recommendation calls a key’s allowed lifetime its cryptoperiod, “the time span during which a specific key is authorized for use.” OWASP’s Secrets Management Cheat Sheet says to “regularly rotate secrets so that any stolen credentials will only work for a short time,” and says the lifetime “could be from minutes” to “years” depending on a secret’s function and what it protects. Neither quote hands you a number for your own keys, so the interval is one the team will actually keep. My reading: a key nobody can rotate is a bigger risk than a key that is old.
The six steps further down are the credential rotation checklist; the rest of this page is what each row needs before you run them. In my audits, the Secrets & Credentials pillar averaged 84.4 out of 100 across 21 third-party apps, the best average of the 12 pillars, and 6 of those 21 apps shipped a real secret. Those 21 are the 11 public and 10 held-out third-party apps I audited in June and July 2026: a selected set, not a random sample and not a rate for AI-built apps in general.
What goes wrong without it
If rotating a key broke the app, the table below names the likely picture and the runbook column that would have caught it. Each row is a pattern, not a report of a specific incident.
| What you see | Why | The runbook column that prevents it |
|---|---|---|
| The web app works, but the nightly job or an edge function fails its first call with an authentication error | That dependent still holds the old key | Every place it is read |
| You changed the variable in the host’s dashboard, and the running build still sends the old value | The value was read at build time and needs a redeploy | Every place it is read, with the redeploy in step 3 |
| Every request fails between the revoke and the deploy | The old key was revoked first, with no overlap | Two live keys at once |
| The live app breaks after a rotation that went fine in testing | The test key and the live key, or two projects, were confused | Credential and provider |
| Nobody can do an urgent rotation | The person who knew has left, and the key sits in an account the company does not hold | Owner account |
The build-time trap in row two, and setting a new value in every environment, are covered step by step in rotating a credential across every environment. When you cannot tell which key the provider is refusing, start with which of your API keys is being refused. Row five is an account problem before it is a key problem: see when a developer owns your hosting account.
Take row one as a case. A founder rotates a payment provider’s secret key in the dashboard, chooses to expire the old key now, updates the web app’s variable and redeploys. A scheduled job that reads the key from its own configuration runs that night with the old key, which the provider no longer accepts, and nothing listed the job as a dependent. On Stripe, picking Now in that expiration dropdown means “the old key is deleted.” The point of the case: the dependents list comes first, and the old key goes only after its request volume has been at zero for a full cycle of the slowest dependent.
A key that is already public is a different job. That is not a rotation but a revoke with no overlap, and what to do in the first hour after an API key leaks covers it. In the same selected set of audits, three apps had a real secret permanently in git history: a webhook signing secret, a live AI-provider key, and a Stripe test key with its webhook secret. Finding keys like those is the job of secret scanning for a small app.
How to rotate API keys safely: the overlap, step by step
Safe API key rotation is 6 steps in a fixed order: confirm the dependents list, create a second key with the same permissions, store it everywhere the old one is read, check each dependent, wait until the old key’s last-used time goes quiet, then revoke it.
- 01 Read the runbook row and confirm the dependents list: search the repository, each host's variable screen and CI for the variable name, never the value
- 02 Create a second key with the same permissions as the first, never broader
- 03 Store the new key in every place the row lists, under the same variable name, then redeploy or reload each dependent that needs it
- 04 Run the row's check against each dependent: a real request, a real webhook, a real job run
- 05 Watch the old key until its request log or last-used time stays quiet for one full cycle of the slowest dependent
- 06 Revoke the old key and write the date in the row
The order is what lets you rotate API keys without downtime: the old key keeps serving every request until nothing uses it, so a missed dependent shows up as traffic on the old key, not as an outage.
Step 1 searches for the name because the name is safe to paste into a search box and the value is not. A variable called PAYMENTS_SECRET_KEY turns up in the code that reads it, in the host’s environment screen and in the CI workflow file. Each hit that reads the key is a dependent.
Step 2 keeps the blast radius where it was: the new key gets exactly the old key’s permissions, compared side by side before it goes anywhere. Stripe’s guidance for its own keys is to “grant server-side keys only the permissions your integration needs.”
Step 3 is where the environment detail lives: every host, every environment and every job gets the new value, and anything that read the variable at build time needs a redeploy before it sees it. The environment variables article linked above walks through that per host.
Step 5 is the step that catches the job nobody listed. Stripe’s guide to API keys gives the same advice in its own words: “Before the old key expires, check its request logs, and expire it only after its request volume has been at zero for a few hours or days.” In the Stripe dashboard those logs are one click from the key: the overflow menu, then View request logs. OpenAI’s project API key reference returns a last_used_at timestamp for each key, which does the same job. Where a provider shows neither a request log nor a last-used time, the row says so, and my working rule is to wait a full cycle of the slowest dependent with each dependent’s check passing on the new key. The slowest dependent is something like the weekly report or the monthly invoice job, not the web app.
Stripe’s rotate option keeps “both the old and new keys” working “for up to 7 days.” If the slowest dependent runs less often than weekly, Stripe’s docs point to the longer route: create a new key yourself, migrate to it, then expire the old one when you are done. Where possible, Stripe also suggests using the new key from a small subset of servers first and watching the server logs for errors before deploying further.
Of the best practices for key rotation, my working rule puts two first: step 1, because a dependents list nobody searched for is a guess, and step 5’s wait for the slowest dependent.
Two keys at once: the overlap window by credential type
An overlap window is the period when the old and new credential both work. API keys, webhook secrets, database users, signing keys and certificates each get one a different way, and one rule holds for all five: revoke only after the slowest dependent has moved to the new credential.
| Credential type | How the overlap works | What to watch before revoking |
|---|---|---|
| Provider API key | Stripe’s rotate option, described above, keeps both keys working for its chosen expiry; an OpenAI project can hold several enabled keys, each with its own last_used_at | The old key’s request log or last-used time |
| Webhook signing secret | Stripe keeps one secret per endpoint, different for test and live, and rolling it can delay the old secret’s expiry for up to 24 hours while Stripe signs with each active secret; where a provider has no overlap, my working rule is to accept either secret in the handler for the window | Deliveries in the endpoint’s Event deliveries tab passing on the new secret |
| Database password | A second database user with the same grants, the app moved to it, then the first user dropped; AWS Secrets Manager’s alternating-users strategy leaves “both user and user_clone credentials” valid after rotation | Sessions still connected as the first user |
| JWT or session signing key | Publish the new key and keep verifying with both until the longest-lived token has expired; Supabase keeps “the trust relationship with both keys” after a rotation | Tokens signed by the old key that have not expired yet |
| TLS certificate | Install the new certificate beside the old files and reload; the old one keeps working until it expires | The live endpoint serving the new serial |
Managed stores such as AWS Secrets Manager, HashiCorp Vault and Azure Key Vault are one more place in the row, not a replacement for it. AWS Secrets Manager’s rotation guide defines rotation as “the process of periodically updating a secret,” and its alternating-users strategy is the one it calls “appropriate for applications that require high availability.” For signing keys, Supabase’s JWT signing keys page gives a concrete wait: with a 1-hour access token expiry, “wait at least 1 hour and 15 minutes before revoking the legacy JWT secret.”
Some credentials allow only one live value at a time. AWS describes the single-user database strategy that way: “there is a short period of time between when the password in the database changes and when the secret is updated,” with a low risk of denied calls in between. For a one-live-key credential, schedule the short failure window for the quietest hour, and write in the row whether the provider can restore the old value; where the provider’s docs do not say, the cell says exactly that.
Rotate API keys on Supabase: publishable and secret keys
Supabase has two key systems, and they rotate differently. The current system has publishable keys (sb_publishable_...) for anything you ship and secret keys (sb_secret_...) for backend components; the legacy system has the long-lived JWT keys anon and service_role. Supabase says it “is deprecating the anon and service_role keys by the end of 2026.”
Current secret keys and the legacy service_role key are elevated server credentials that bypass RLS. Supabase says a secret key must never appear in “a browser, even on localhost.” That makes them the first row of a Supabase app’s runbook; why they matter so much is under keep the service_role key server-side.
A current secret key rotates the way the six steps describe. Supabase’s documented procedure, written for a leaked or compromised key, is to create a new secret key in the Settings > API Keys section of the Dashboard, replace the old one “everywhere your application uses it,” confirm “that every component now uses the new key,” and only then retire the old one. Supabase also advises: “Use a separate secret key for each backend component. If one component leaks its key, you only rotate that one.” For an Edge Function, step 3 is a secrets update, not a deploy: Edge Function secrets set in the dashboard or with supabase secrets set are available immediately with no redeploy.
Moving from the legacy keys to publishable and secret keys is itself a rotation, and Supabase’s API keys guide turns it into a migration you can run by the six steps: creating the new keys “adds it alongside your existing anon and service_role keys without affecting them,” so the overlap is built in until you disable the legacy pair.
The legacy JWT secret is a separate rotation. The anon and service_role keys “are also valid JSON Web Tokens, signed by the legacy JWT secret,” so Supabase says that “before you revoke the legacy JWT secret, you must disable the anon and service_role” keys. The migration to signing keys “does not cause downtime,” but an app that verifies every JWT against the legacy secret with a library like jose or jsonwebtoken “might break” when you continue the rotation.
Rehearse credential rotation in staging
A rotation rehearsal runs all 6 steps in staging with a test credential, skips one dependent on purpose to see the failure, and is timed. The output is the list of affected services and the checks, written into the runbook row.
- 01 Create a test credential: a sandbox or test-mode key, or a throwaway key made for the rehearsal
- 02 Run all six rotation steps against staging, in order
- 03 Skip one dependent on purpose and note what fails and how long it takes to notice
- 04 Time the whole rotation, from the first search to the revoke
- 05 Write the affected services and each check into the runbook row
The rehearsal never touches the live key. On Stripe, sandbox keys start with pk_test_, rk_test_ and sk_test_, so the prefix alone tells you which one you are holding. A staging environment with its own keys is the precondition, and setting that up is covered in separate keys for development, staging and production.
My reading of a clean first rehearsal: if it found nothing missing on the first try, the dependents list may not have been tested at all.
When the new key fails: the rollback
A rotation rollback is 3 lines: put the old value back under the same variable name, redeploy the same dependents, run the same check. It works only while the old key is still live, so the revoke comes last.
Keep the old value in the secrets store, not in a chat message, until the revoke. My working rule: after the revoke there is usually no rollback, only a second rotation, so the revoke gets its own go or no-go.
Some providers write down which retirements can be undone, and the row’s rollback cell should say which case applies. Supabase deactivates legacy anon and service_role keys instead of deleting them, and “Deactivation is reversible, so you can re-enable them if you find a client you missed”; deleting a current Supabase secret key “can’t be undone.” Supabase’s signing keys can move from previously used or revoked “back to being a standby key,” so that rotation has a way back too.
Two neighbors of this step have their own pages: what an API key is, if the row has you unsure which kind you hold, and how to cap the monthly usage on a metered API, which a metered key should get on the new value before it takes traffic.
Rotating a certificate: prove the new key and certificate match before the swap
A certificate is the one credential where the overlap comes free, because the old certificate keeps working until it expires. The risk is the install: a certificate that does not match its private key will not load, and finding that out at the swap is how a routine renewal becomes an outage.
Before you run any OpenSSL check on a certificate and key, one warning: never paste a private key into an online matcher, validator or decoder. My working rule is that a key pasted into a website is exposed and gets replaced. Every command below runs on your own machine, against your own files or your own site, and each one is quoted from OpenSSL’s manual pages, not from a run of mine.
To validate the SSL certificate and key before the swap, work in this order: verify the chain with openssl verify and the intermediate file, install the new files beside the old ones, run the web server’s own config test, reload rather than restart, then confirm the live endpoint serves the new serial with openssl s_client. The openssl verify step checks the certificate against its chain, a different job from checking that certificate and key belong together. The table sums up how to test an SSL certificate and private key at each stage.
| What you are checking | The command’s subject | What a pass looks like |
|---|---|---|
| The chain | openssl verify -untrusted intermediate.pem new-cert.pem | No error line; the manual shows only the error form |
| The pair | The public key read from the certificate and from the private key | Two identical hashes |
| The live endpoint | openssl s_client -connect example.com:443 -servername example.com < /dev/null, piped to openssl x509 -noout -serial | The serial number of the new certificate |
File types, key length and the HTTPS setup itself are covered in how to add HTTPS to a website and keep it.
How can I check if a certificate and private key match using OpenSSL?
A certificate and private key match when the public key extracted from each produces the same hash. Run the two OpenSSL commands on your own machine, never in an online matcher, and the comparison works for RSA and ECDSA keys, which the older modulus check does not.
To check if a private key matches a certificate with OpenSSL, compare the public keys, not the files. openssl x509 -pubkey “Prints the certificate’s SubjectPublicKeyInfo block in PEM format,” and, per OpenSSL’s pkey manual page, openssl pkey -pubout restricts the output “to the public components”; hash both.
# 1. Hash the public key inside the certificate
openssl x509 -in new-cert.pem -noout -pubkey | openssl dgst -sha256
# 2. Hash the public key derived from the private key
openssl pkey -in new-key.pem -pubout | openssl dgst -sha256
Identical hashes from the two OpenSSL commands verify that the certificate and private key match; different hashes mean the key on disk is not the one the certificate was issued for. If the key is encrypted, the second command prompts for its pass phrase first.
Why this form and not the modulus comparison: the certificate half of that check is openssl x509 -modulus, which prints “the modulus of the public key contained in the certificate,” but the key half runs openssl rsa, which “processes RSA keys,” while openssl pkey “processes public or private keys.” When you check if an SSL certificate matches a private key, the public-key form covers RSA and ECDSA keys alike.
Checking the key itself: format, algorithm and passphrase
A private key file answers 4 questions offline: is it consistent, which algorithm and size is it, is it passphrase-protected, and is it the only thing in the file. OpenSSL’s pkey command answers all of them.
| Question | Command subject | What a pass prints |
|---|---|---|
| Is the private key valid and consistent? | pkey -check, or rsa -check on an RSA key | The manual says only what the flag checks: “the consistency of a key pair for both public and private components” |
| Which algorithm and size is it? | pkey -text | The key’s components in plain text |
| Is it passphrase-protected? | Any pkey read without -passin | A pass phrase prompt means the key is encrypted |
| Is the key the only thing in the file? | pkey -in a bundle, -out a new file | A new file holding the key alone, in PEM |
# One line per row of the table, run against your own files
openssl pkey -in key.pem -check -noout
openssl pkey -in key.pem -text -noout
openssl pkey -in key.pem -noout
openssl pkey -in bundle.pem -out key-only.pem
The first row answers how to check if a private key is valid: openssl pkey -check is the OpenSSL command to verify a private key. A PEM file that pkey cannot read fails that row too, which makes it a quick way to check if a PEM file is valid. To validate an RSA private key with the older command, openssl rsa -check “checks the consistency of an RSA private key.”
To check the key algorithm, OpenSSL’s -text output prints “the various key components in plain text.” To check if a key has a password, run any pkey command on it without -passin: OpenSSL prompts for a pass phrase when the private key input is encrypted. Typing the pass phrase at that prompt also lets you verify the private key password, so a wrong one fails on your machine and not at the reload.
To check a private key’s format with OpenSSL, read it with pkey: it takes DER, PEM or P12 input, and its output format defaults to PEM. Row four is how you export, or extract, the private key from a PEM bundle with OpenSSL when the key and certificate share one file; then set the new file’s mode so only the server’s user can read it.
The date to watch is the certificate’s, not the key’s: openssl x509 -enddate “Prints out the expiry date of the certificate.” Certificate expiry checks belong to the HTTPS page linked above.
Renew with the same key or a new one
Renewing a certificate with the same key is allowed, and a new key on each renewal is the safer default. Reuse the key only when something pins it. Generate a new one whenever a person with server access has left or a copy of the key may have leaked.
A renewal client reuses the key only when told to. In Certbot’s user guide, --reuse-key means “When renewing, use the same private key as the existing certificate,” and “Not reusing private keys is the default behavior of Certbot.” So to renew a certificate with the same public key, you keep the private key, because the public key is derived from it.
When you renew a certificate, a new key vs the same key comes down to one question: does anything pin the key? A new key limits how long one stolen key stays useful, which is why my working rule makes it the default. Certificate pinning in a mobile app is the usual reason to keep the old one, in my reading. Request a new certificate with the same key only when that answer is yes; otherwise request the certificate with a new key and a new CSR.
A private key certificate request, the CSR, is generated from the private key on your server, and only the public key goes to the CA. To check if a CSR and private key match, use the same comparison: openssl req -in request.csr -noout -pubkey “Prints out the public key,” and you hash it beside the key’s pkey -pubout output. When the two hashes agree, you can verify with OpenSSL that the CSR and private key match before you send the request anywhere.
On Windows Server, Microsoft’s certreq reference gives the syntax to renew a certificate with the same key through certreq as certreq -enroll -cert certId [options] renew [reusekeys]. That page does not describe how to renew a certificate with the same key in the MMC; it uses the Certificates MMC snap-in only to read the thumbprint of the certificate to renew.
To renew a self-signed certificate with the same key, issue a new one from the existing key: openssl req -x509 -new -key key.pem -days <days> -out new-cert.pem puts that key’s public half in a new self-signed certificate.
Public keys: read one from a certificate and check it
A public key is safe to show anyone, which is why it is the thing to compare. To get the public key from a certificate, openssl x509 -in cert.pem -noout -pubkey prints it in PEM. To read a public key file, openssl pkey -pubin -in pub.pem -text -noout prints its components, and the same output lets you view a certificate’s public key once you have saved it to a file.
To check if a private key matches a public key, derive the public half with openssl pkey -in key.pem -pubout and compare it with the one you hold: identical output is how you verify with OpenSSL that the public and private key match.
To validate a public key, my working rule is to check two things: that OpenSSL parses it, and that it is the one you expected, by comparing its fingerprint with one you got through another channel. To validate an RSA public key, or any other, openssl pkey -pubin -pubcheck “checks the correctness of either a public key or the public component of a key pair.”
The public key certificate process in one sentence: a certificate is “an electronic document that uses a digital signature to bind a public key and an identity,” signed by the CA. To verify a public key certificate against its issuer, use openssl verify, which “verifies certificate chains”; comparing keys answers a different question.
What expires, and what warns you before it does
Whether an API key expires on its own depends on the provider and on what was set when the key was made, while a TLS certificate always carries an expiry date. A credential that never expires gets no reminder from anyone, so every row in the runbook that never expires needs its own rotation date and a named owner.
| Credential | Does it expire on its own? | Where to see the date | What warns you |
|---|---|---|---|
| Provider API keys (Stripe, OpenAI, Supabase, Google Cloud) | Stripe: when you expire or rotate it, and a default expiry is not stated in Stripe’s docs. OpenAI: a project key’s expires_at is null “if it does not expire.” Supabase and Google Cloud: not stated in their API key docs | Stripe shows the old key’s remaining time below its name when a rotation sets an expiry time; OpenAI’s expires_at field | Not stated in these providers’ docs |
| GitHub personal access tokens | Only if an expiration was set: GitHub allows infinite lifetimes and recommends an expiration, and it also removes tokens unused for a year | The GitHub-Authentication-Token-Expiration header on API responses, per GitHub’s changelog | An email when a token is about to expire, per GitHub’s changelog |
| OAuth client secrets and cloud service-account keys | Google Cloud: “By default, service account keys never expire.” Google deletes an OAuth client that has been inactive for six months | A service account key’s validBeforeTime in the key list; inactive OAuth clients on the Clients page | For OAuth clients, an email 30 days before the deletion; for service-account keys, not stated |
| TLS certificates | Yes, always: the notAfter date | openssl x509 -noout -enddate | An expiry monitor you set up |
| GPG and PGP keys | Only if an expiry was set: “0 = key does not expire” | The key listing’s “expires:” field | Not stated in the GnuPG handbook |
| SSH keys | Plain key pair: not stated in the ssh-keygen manual. SSH certificate: yes, its validity interval | ssh-keygen -L on the certificate | Not stated |
Two rows need a note. Google Cloud’s API key best practices state no expiry; they say to “Periodically create new API keys, update your applications to use the new API keys, and delete the old keys.” GitHub’s token expiration docs recommend setting an expiration on a personal access token, and “Upon reaching your token’s expiration date, the token is automatically revoked.” For certificates, the warning is yours to build: uptime monitoring with a certificate-expiry alert.
To check a GPG key’s expiration date, list the key: the GnuPG handbook shows an “expires:” field in the listing, which reads “never” on a key made with no expiry. You check a PGP key’s expiration date the same way, in the same listing. To verify a GPG or PGP key someone sent you, compare the fingerprint from gpg --fingerprint with the one its owner gives you over a second channel; the handbook says this “may be done in person or over the phone or through any other means as long as you can guarantee that you are communicating with the key’s true owner.” To validate a PGP public key, the handbook pairs that fingerprint check with a signature: “A key is validated by verifying the key’s fingerprint and then signing the key to certify it as a valid key.”
To check an SSH key’s validity, know which kind you hold. The ssh-keygen manual page says -l will “Show fingerprint of specified public key file,” while an SSH certificate carries a validity interval set with -V when it is signed and printed with -L. To check a public key’s validity or its expiration date, look at the certificate around it, because that is where the date lives.
The calendar half of the runbook, as my working rule: the “last rotated” column plus one calendar entry per row, owned by a named person, because a key that never expires is the one that needs a rotation date most.
How to verify it
A rotation runbook is verified by 5 checks: the dependents list survives a search, a test credential is rotated in staging and timed, the old test key is refused after the revoke, the rollback works once, and the evidence is kept.
- 01 Pick one row and search the repository, hosts and CI for its variable name; any place that reads the key and is missing from the row fails the list. Evidence: the search output
- 02 Rotate a test credential in staging by the six steps, timed. Evidence: the rehearsal notes with the services affected
- 03 Call the old test key after the revoke. Evidence: the refusal, with its status code and time
- 04 Before the revoke, run the rollback once and confirm the old value works again. Evidence: the passing check
- 05 For a certificate, keep the two matching hashes and the live serial after the reload. Evidence: the saved command output
In check 1, a hit in a README or an example env file does not count as a reader. Check 3 keeps whatever refusal the provider returns rather than expecting one exact code; Stripe, for one, answers an expired key with an authentication error. My working rule: a row with no rehearsal date is not verified.
In the Production Hardening Sprint, deliverable 2.6 is verified this way: we rehearse rotation with a test credential and record the affected services and checks.
Where the sprint does this
Key rotation is deliverable 2.6 of the 123 in the Production Hardening Sprint: we document how to rotate each key, update its dependents, verify the change, and recover from a failed rotation, and it is verified the way the section above describes. Deliverable 13.3 documents deployment, rollback, key rotation, backup restoration, and the response to each operational alert, and we verify it by walking through the runbooks against the delivered configuration and referencing the rehearsal evidence. The result for every scope item, the work completed and its verification evidence go into the production readiness report, deliverable 13.1, which we verify by accounting for all 123 IDs, keeping failures visible until resolved and explaining genuine non-applicable items. Every line is on the published scope.
Common questions about rotating keys and certificates
What should I do if my certificate and private key do not match?
Find the key the certificate was issued for: run the public-key comparison against every private key on the server that made the CSR. If none matches, generate a new key and a new CSR and ask the certificate authority to reissue the certificate; whether a reissue costs anything is set by that authority’s own terms.
Where can I find the private key for my SSL certificate?
On the server or machine that generated the CSR, because the key is created there alongside the request and only the public key is sent to the certificate authority. On a managed host that issues certificates for you, the platform holds the key, in my reading, and you may never see it.
How do I decrypt a private key?
Run openssl pkey -in enc.key -out plain.key and type the pass phrase when prompted; OpenSSL’s manual gives that form as the way “To remove the pass phrase on a private key.”
A server that has to restart unattended usually needs the unencrypted key, in my reading, and file permissions then do the job the pass phrase did: only the server’s user reads the file.
How to combine private key and certificate using OpenSSL?
For a PEM bundle, concatenate the files in the order your server expects. For a PKCS#12 file, run openssl pkcs12 -export -in cert.pem -inkey key.pem -out bundle.p12, where -export means “a PKCS#12 file will be created rather than parsed.” Which file type your server wants is covered on the HTTPS page.
Does a certificate need a private key?
A server needs the private key that matches its certificate; the certificate alone is public and cannot prove the server’s identity without it. A CA certificate in your trust store has no private key on your machine, and that is expected, which answers the Windows “has private key” question.
Is RSA or Ed25519 better?
For a publicly trusted TLS certificate, the choice is RSA or ECDSA, not Ed25519: the CA/Browser Forum Baseline Requirements allow RSA keys of at least 2048 bits and ECDSA keys on the NIST P-256, P-384 or P-521 curves, and “No other algorithms or key sizes are permitted.” For SSH keys, Ed25519 is a common modern choice; that part is my reading, not a rule from a standard.
The checks in this guide show you where the app is open. The sprint below closes those gaps, tests the result and writes the evidence down.
Built it with AI. Now it has to hold up for real customers.
The Production Hardening Sprint takes the app you already have and builds the production foundation underneath it. Authentication and access rules, payments that stay consistent, error handling, monitoring, backups, automated tests and a documented handover. Our engineers work inside your existing codebase for ten working days. All 123 deliverables are included, and you get the evidence for each one.
See the Production Hardening Sprint →
$2,500 fixed price · 10 working days · One codebase