Repository navigation
Making a server 2
OpenSSL Certificate Authority Lab — Rocky 9.6 Runbook.md
OpenSSL Certificate Authority Lab — Rocky 9.6 Runbook
Oct 1, 2026 · @Juniper
Overview
This builds a private Certificate Authority on one Rocky 9.6 VM and uses it to issue a TLS certificate to an Apache web server on a second VM.
Host 1 — Web Server Host 2 — Certificate Authority
Example address 192.168.1.10 192.168.1.20
Running now httpd on HTTP nothing
Generates websrv.key, websrv.csr cakey.pem, cacert.pem
Installs mod_ssl yes no
Opens port 443 yes no
Substitute your real IPs throughout. All commands run as root.
Sticky-note constraints
Organization Name, Organizational Unit Name and Common Name are Joyce310 on both hosts, every time you are prompted.
Country and State must also match between the CA certificate and the web server request. The default policy_match policy rejects a mismatch, and this is the single most common failure in the lab.
Record the password you set on each private key. There is no recovery.
"No passphrases" refers to the two optional CSR attributes — A challenge password and An optional company name. Press Enter to leave both blank. It does not mean skipping the key passwords.
Flow of the lab
Build the CA directory structure on Host 2.
Generate the CA key and self-sign cacert.pem.
Generate the web server key and a certificate signing request on Host 1.
scp the request to Host 2.
Sign it, producing websrv.crt.
scp the signed certificate back to /root on Host 1. Save point.
Configure Apache for TLS and verify.
Phase 1 — Build the CA directory (Host 2)
Rocky 9 often does not ship /etc/pki/CA, so create it. mkdir -p makes the parent for you.
mkdir -p /etc/pki/CA/{certs,crl,newcerts,private}
chmod 700 /etc/pki/CA/private
touch /etc/pki/CA/index.txt
echo 1000 > /etc/pki/CA/serial
restorecon -Rv /etc/pki/CA{certs,crl,...} is brace expansion — bash rewrites it into four separate paths. index.txt must exist and start empty. serial must hold a hex number; openssl ca issues that serial, then increments it. restorecon fixes SELinux labels, worth running because you created the directory by hand rather than getting it from an RPM.
/etc/pki/CA is not /etc/pki/ca-trust
Two unrelated things that live next door to each other.
Path Purpose
/etc/pki/CA/ Working directory for running a CA
/etc/pki/ca-trust/ The system trust store — the list of CAs this machine believes, used by curl, git, Python, Firefox
Seeing ca-trust but not CA is normal, not a broken install. ca-trust becomes relevant only at the verification stage.
Confirm the config points where you think
grep -A5 '\[ CA_default \]' /etc/pki/tls/openssl.cnf
openssl version -dYou want dir = /etc/pki/CA. If it reads dir = ./demoCA, either edit that line or pass -config to every openssl ca and openssl req command.
What lives where
Path Purpose
private/cakey.pem CA private key — the crown jewels
cacert.pem CA self-signed public certificate
index.txt Flat-file database of every certificate issued
serial Next serial number to hand out
newcerts/ Archive copy of each signed cert, named by serial
certs/ Where the issued certificate is written
Phases 2–3 — CA key and self-signed certificate (Host 2)
Generate the CA private key
openssl genrsa -aes256 -out /etc/pki/CA/private/cakey.pem 2048
chmod 600 /etc/pki/CA/private/cakey.pemgenrsa generates an RSA keypair — the private key file mathematically contains the public key too. -aes256 encrypts the file, which is what triggers Enter PEM pass phrase. 2048 is the modulus size in bits.
The passphrase prompt is asking you to invent a password, not recall one. Type anything of 4 characters or more, twice. Nothing echoes while you type; that is normal, not a frozen terminal. A worked example is Joyce310CA! — but whatever you type is what it becomes, so write it down. Losing this password means rebuilding the CA from scratch.
Self-sign the CA certificate
openssl req -new -x509 -days 3650 \
-key /etc/pki/CA/private/cakey.pem \
-out /etc/pki/CA/cacert.pem-x509 is the twist: instead of emitting a signing request, req self-signs and emits a finished certificate. That is what makes a root CA. -days 3650 is a ten-year lifetime, normal for a root. -key reuses the existing key rather than generating a new one.
Answer the prompts exactly
Country Name (2 letter code) [XX]:US
State or Province Name []:<your state>
Locality Name []:<your city>
Organization Name []:Joyce310
Organizational Unit Name []:Joyce310
Common Name []:Joyce310
Email Address []:<blank is fine>
Write down the Country and State you used. Phase 4 must match them. Verify
openssl x509 -in /etc/pki/CA/cacert.pem -noout -subject -issuer -datesSubject and issuer should be identical — that is the signature of a self-signed root. Dates should span ten years. Phase 4 — Web server key and CSR (Host 1)
openssl genrsa -aes256 -out /etc/pki/tls/private/websrv.key 2048
chmod 600 /etc/pki/tls/private/websrv.key
openssl req -new \
-key /etc/pki/tls/private/websrv.key \
-out /etc/pki/tls/certs/websrv.csrThe second command has no -x509, so the output is a signing request rather than a certificate. A worked example password for this key is Joyce310Web!; again, record whatever you actually type.
Prompt answers
Identical to Phase 3 — Country, State and Joyce310 for Organization, Organizational Unit and Common Name. Then:
A challenge password []: <- press Enter
An optional company name []: <- press Enter
Those two are the "no passphrases" hint. They are vestigial CSR attributes that serve no purpose here, and filling them in can cause complaints at signing time. Check before shipping
openssl req -in /etc/pki/tls/certs/websrv.csr -noout -text | head -20-noout suppresses the base64 blob, -text prints the human-readable decode. Confirm the Subject line reads C=US, ST=..., O=Joyce310, OU=Joyce310, CN=Joyce310.
What a CSR contains
Three things: the web server's public key, the subject fields you typed, and a signature made with the private key proving you hold it. None of that is secret. The private key itself never leaves Host 1 — not in this lab, not in production.
Testing a password you are unsure of
openssl rsa -in /etc/pki/tls/private/websrv.key -noout -checkRSA key ok means you guessed right. A bad-decrypt error means wrong password. Losing the web server key password costs twenty minutes — regenerate the key and CSR and get a new certificate signed. Losing the CA key password ends the CA.
Phase 5 — Transfer the CSR to the CA
Pre-flight on Host 2
systemctl status sshd
ss -lntp | grep ':22 '
ip -brief addr showHealthy ss output looks like this — two lines, because sshd binds IPv4 and IPv6 separately:
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=912,fd=3))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=912,fd=4))
-l listening only, -n numeric ports, -t TCP only, -p owning process. -p needs root; as a normal user the users:(...) field comes back empty, which looks alarming and is not. The trailing space in grep ':22 ' stops 2222 and 8022 from matching.
The command (run on Host 1)
scp /etc/pki/tls/certs/websrv.csr root@192.168.1.20:/etc/pki/CA/Piece Meaning
scp Secure copy — a file transfer riding inside an SSH session
/etc/pki/.../websrv.csr Source; a local path, so no colon
root@ Account to log in as on the far side
192.168.1.20 The CA
: Separator meaning "everything after this is a remote path". Omit it and scp makes a local file literally named root@192.168.1.20
/etc/pki/CA/ Destination directory. Trailing slash means "put it in here" rather than "rename it to this"
Source then destination, same as cp.
Root login will probably be refused
RHEL 9 and Rocky 9 ship PermitRootLogin prohibit-password, so root may log in with a key but never with a password. If you see Permission denied (publickey,gssapi-keyex,...) with no password prompt at all, that is this, and your password being correct is irrelevant — it was never asked for.
The clean workaround, which needs no config change:
# on Host 1
scp /etc/pki/tls/certs/websrv.csr labuser@192.168.1.20:/tmp/
# on Host 2
sudo mv /tmp/websrv.csr /etc/pki/CA/
sudo chown root:root /etc/pki/CA/websrv.csrOr enable it on Host 2 by setting PermitRootLogin yes in /etc/ssh/sshd_config and running systemctl restart sshd. Check /etc/ssh/sshd_config.d/*.conf first — a drop-in file there loads earlier and its setting wins, so editing the main file alone can appear to do nothing.
Confirm the subjects match before signing
openssl req -in /etc/pki/CA/websrv.csr -noout -subject
openssl x509 -in /etc/pki/CA/cacert.pem -noout -subjectCountry, State and Organization must agree. Catching a mismatch here saves a cryptic policy_match error mid-signing. If they differ, regenerate just the CSR on Host 1 with corrected answers and re-copy — the key does not need redoing.
Phase 6 — Sign the certificate (Host 2)
openssl ca \
-in /etc/pki/CA/websrv.csr \
-out /etc/pki/CA/certs/websrv.crt \
-days 365ca is the signing engine. It reads openssl.cnf, finds the CA key and certificate, checks the request against policy_match, consumes a serial number, writes a row into index.txt, and drops an archive copy in newcerts/.
It prompts for the CA key password, displays the certificate details, then asks two questions:
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]:y
Answer y to both. Answering n to either, or pressing Ctrl-C, leaves no output file — a common reason websrv.crt later turns out not to exist.
Verify the chain
openssl verify -CAfile /etc/pki/CA/cacert.pem /etc/pki/CA/certs/websrv.crtYou want : OK.
Confirm the signing actually recorded
cat /etc/pki/CA/index.txt
ls -l /etc/pki/CA/newcerts/A line beginning V in index.txt means a valid certificate was issued. openssl ca always writes newcerts/<serial>.pem. If 1000.pem is there but certs/websrv.crt is not, the signing worked and only the -out path went somewhere unexpected:
cp /etc/pki/CA/newcerts/1000.pem /etc/pki/CA/certs/websrv.crtIf both are empty, re-run the signing command. Phases 7–8 — Return the certificate and the save point Copy it back (run on Host 2)
scp /etc/pki/CA/certs/websrv.crt root@192.168.1.10:/root/Same root-login wall as Phase 5, now in the opposite direction — Host 1 refuses root password auth for the identical reason. Use the same workaround:
# on Host 2
scp /etc/pki/CA/certs/websrv.crt labuser@192.168.1.10:/tmp/
# on Host 1
sudo mv /tmp/websrv.crt /root/
sudo chown root:root /root/websrv.crtWatch for the 100% completion line. If scp printed nothing or errored, the transfer did not happen regardless of how the command looked.
If the certificate is not in /root
sudo ls -la /root//root is mode 700, so as a normal user ls /root returns permission denied — which reads like "not there" but is not. Use sudo, or sudo -i first.
Still missing? Widen the net:
sudo find / -name "websrv.crt" 2>/dev/null2>/dev/null discards the permission-denied noise from /proc and friends. Also check /root the file versus / the filesystem root — a destination written as :/root with no trailing slash, against a non-existent /root, would have created a file named root at /.
Confirm it is the real thing
sudo openssl x509 -in /root/websrv.crt -noout -subject -issuer -datesSubject CN=Joyce310, issuer your CA's Joyce310, dates spanning the next year.
Final output — the three cat commands
On Host 2:
cat /etc/pki/CA/cacert.pemOn Host 1:
cat /etc/pki/tls/private/websrv.key
cat /root/websrv.crtThe key shows Proc-Type: 4,ENCRYPTED in its header — that is the AES-256 password doing its job, and it is expected. If you later strip the password so Apache can start unattended, keep a copy of the encrypted original in case the encrypted header is what your instructor wants to see.
Phases 9–13 — Apache TLS (Host 1 only)
Everything below runs on the web server. Host 2 needs nothing installed — a CA is an offline signing station, and its work ended when the certificate was copied back.
Place the files
cp /root/websrv.crt /etc/pki/tls/certs/
chmod 644 /etc/pki/tls/certs/websrv.crt
chmod 600 /etc/pki/tls/private/websrv.key
chown root:root /etc/pki/tls/private/websrv.key
restorecon -Rv /etc/pki/tls/certs/websrv.crt /etc/pki/tls/private/websrv.key
ls -Z /etc/pki/tls/certs/websrv.crt /etc/pki/tls/private/websrv.keyThe key was generated in place in Phase 4, so it is likely already correct — verify rather than assume.
The permissions differ deliberately. The certificate is public; every browser that connects receives a copy, so 644 is right. The key is the secret half, so 600. Apache starts as root, reads the key, then drops to the apache user, so worker processes never need access.
Labels should read cert_t. A file that came via /tmp may carry user_tmp_t, and Apache will be denied read access with an error that looks nothing like a permissions problem.
Confirm the key and certificate are a pair
openssl x509 -noout -modulus -in /etc/pki/tls/certs/websrv.crt | openssl md5
openssl rsa -noout -modulus -in /etc/pki/tls/private/websrv.key | openssl md5Two identical hashes means they match. Different hashes means the certificate was signed from a different key than the one sitting here. Install mod_ssl
dnf install mod_ssl -y
httpd -M | grep sslThis drops in /etc/httpd/conf.d/ssl.conf and mod_ssl.so. You want ssl_module (shared) in the output.
Edit ssl.conf
cp /etc/httpd/conf.d/ssl.conf /etc/httpd/conf.d/ssl.conf.orig
vi /etc/httpd/conf.d/ssl.confFind these two directives, which ship pointing at Rocky's placeholder, and change them:
# from:
SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
# to:
SSLCertificateFile /etc/pki/tls/certs/websrv.crt
SSLCertificateKeyFile /etc/pki/tls/private/websrv.keyIn vi, /SSLCertificateFile jumps to the first match and n finds the next. Both live inside the <VirtualHost _default_:443> block. Make sure you are editing the live lines, not the commented examples above them — a leading # means Apache ignores the line entirely. While you are in there, set ServerName to match the hostname you will browse to.
apachectl configtestSyntax OK is what you want.
Firewall
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
firewall-cmd --list-all--permanent writes to the on-disk config but does not touch the running firewall — that is why --reload follows, re-reading permanent rules into the live ruleset. Omit --permanent and the rule works immediately but vanishes on reboot. https is a named alias for tcp/443.
The encrypted key will block startup
Because you used -aes256, Apache needs the password to read the key, and systemctl gives it nowhere to prompt. Expect a failure, with journalctl -xeu httpd complaining about loading the server certificate or a bad decrypt.
The standard fix is to strip the password:
cd /etc/pki/tls/private
cp websrv.key websrv.key.encrypted
openssl rsa -in websrv.key.encrypted -out websrv.key
chmod 600 websrv.key
restorecon -v websrv.keyopenssl rsa reads the encrypted key, prompts for the password one last time, and writes it back out unencrypted. Keep the .encrypted copy for your cat output if the assignment expects the Proc-Type: 4,ENCRYPTED header.
The tradeoff: anyone who can read that file now owns the key with no further obstacle, which is why mode 600 matters more once the password is gone. Essentially every production web server runs this way — the password only protects against offline theft of the file, and it makes unattended reboots impossible.
The alternative is SSLPassPhraseDialog exec:/path/to/script, where the script echoes the password. That preserves the encrypted key but is largely security theater, since the password then sits in a file too.
Start it
systemctl restart httpd
systemctl enable httpd
systemctl status httpd
ss -lntp | grep :443Verification and trust
curl error 60 is expected, not a failure
It means TLS negotiated fine — curl received your certificate, read it, and could not find Joyce310 among the CAs it trusts. Nobody vouches for a CA you invented an hour ago.
TLS verification runs two independent checks, and both must pass:
Chain of trust — is this signed by a CA I trust? Fixed by the trust store.
Name match — does the hostname in the URL appear in the certificate? Nothing in the trust store fixes this.
Quick confirmation it is only trust
curl -k https://localhost-k (also spelled --insecure) skips verification. If the page renders, the crypto works and trust is the only gap. The wget equivalent is wget --no-check-certificate.
Install your CA into the trust store
# on Host 2
scp /etc/pki/CA/cacert.pem labuser@192.168.1.10:/tmp/
# on Host 1
sudo cp /tmp/cacert.pem /etc/pki/ca-trust/source/anchors/joyce310-ca.pem
sudo update-ca-trust
curl https://localhostupdate-ca-trust reads everything in source/anchors/ and regenerates the bundles under extracted/ that curl, Python, git and others consult. Only ever drop files into anchors/ — extracted/ gets overwritten.
If error 60 persists, read the new message
curl -v https://localhost 2>&1 | grep -iE "subject|issuer|SSL certificate"A message about the certificate's name not matching is the second problem. Your CN is Joyce310 but you connected to localhost, and curl requires the hostname you typed to appear in the CN or SAN.
Asking for the certificate by name
Joyce310 is not a real hostname — no DNS record, no /etc/hosts entry — so curl https://Joyce310 alone returns Could not resolve host. --resolve lets you use the name in the URL while telling curl where to actually connect:
curl --resolve Joyce310:443:127.0.0.1 https://Joyce310The format is strictly HOST:PORT:ADDRESS, colon-separated, no spaces. Curl builds a fake DNS entry that lives only for this one command — nothing is written to disk.
What happens: curl parses the URL, sees hostname Joyce310, checks its --resolve list instead of DNS, opens a connection to 127.0.0.1:443, sends SNI: Joyce310 in the handshake, receives websrv.crt, validates the chain, then compares Joyce310 against CN=Joyce310 and passes.
The port in --resolve must match the port curl is actually using. Joyce310:80:127.0.0.1 with an https:// URL silently does nothing.
To see verification happen:
curl -v --resolve Joyce310:443:127.0.0.1 https://Joyce310The payoff line is SSL certificate verify ok. From Host 2, point at the real interface instead: --resolve Joyce310:443:192.168.1.10.
For a one-off without touching the trust store, --cacert replaces the system bundle for that command:
curl --cacert /etc/pki/CA/cacert.pem --resolve Joyce310:443:127.0.0.1 https://Joyce310Other ways to fix the name problem
Approach Command Scope
--resolve curl --resolve Joyce310:443:127.0.0.1 https://Joyce310 One command, no root, port-specific, no browser
--connect-to curl --connect-to Joyce310:443:localhost:443 https://Joyce310 Same, but takes a hostname on the right side
/etc/hosts echo "127.0.0.1 Joyce310" | sudo tee -a /etc/hosts System-wide, persists, needs root, works in browsers
System hostname hostnamectl set-hostname Joyce310 Also lines up Apache's ServerName
Real DNS An A record for Joyce310 Overkill for two VMs
Verify without curl
openssl s_client -connect localhost:443 -servername Joyce310 \
-CAfile /etc/pki/CA/cacert.pem </dev/null-servername sets SNI manually, -CAfile supplies the trust anchor. Verify return code: 0 (ok) is the most direct proof your CA chain works, with no name-matching interference.
The real fix — reissue with a SAN
The certificate has no Subject Alternative Name. Curl still falls back to checking CN when no SAN is present, but Firefox and Chrome dropped that fallback years ago and will reject the certificate regardless of trust store or hostname. For the assignment as written, -k or --resolve demonstrates the server works. For a green padlock in a browser, reissue.
On Host 2:
cat > /etc/pki/CA/websrv_ext.cnf <<'EOF'
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = Joyce310
DNS.2 = localhost
DNS.3 = web.example.com
IP.1 = 127.0.0.1
IP.2 = 192.168.1.10
EOF
openssl ca -in /etc/pki/CA/websrv.csr \
-out /etc/pki/CA/certs/websrv.crt \
-days 365 \
-extfile /etc/pki/CA/websrv_ext.cnfDirective Effect
basicConstraints = CA:FALSE This certificate cannot sign others — it is an end-entity cert
keyUsage Limits the key to signing handshakes and encrypting session keys
extendedKeyUsage = serverAuth Marks it valid for TLS server identity specifically
subjectAltName The list of names this certificate legitimately answers to
List every name and IP you might connect by; a client matches against any entry. You will hit TXT_DB error number 2 because a certificate with that subject already exists — either echo "unique_subject = no" > /etc/pki/CA/index.txt.attr or revoke the old one with openssl ca -revoke /etc/pki/CA/newcerts/1000.pem. Then scp the new certificate over, restorecon, and restart httpd. Same key, so no new CSR.
Files created
Host Path Contents
CA /etc/pki/CA/private/cakey.pem Encrypted RSA-2048 CA private key. Never leaves Host 2
CA /etc/pki/CA/cacert.pem Self-signed root certificate, 10-year validity
CA /etc/pki/CA/index.txt One line per issued certificate — status, serial, subject
CA /etc/pki/CA/serial Next serial number, incremented after each signing
CA /etc/pki/CA/newcerts/1000.pem Archive copy of the web server certificate
CA /etc/pki/CA/certs/websrv.crt The issued certificate
CA /etc/pki/CA/websrv.csr The request, as received by scp
Web /etc/pki/tls/private/websrv.key RSA-2048 server key, mode 600
Web /etc/pki/tls/certs/websrv.csr The request; disposable once signed
Web /root/websrv.crt The signed certificate at the save point
Web /etc/pki/tls/certs/websrv.crt Working copy Apache reads, mode 644
Web /etc/httpd/conf.d/ssl.conf Edited by you; .orig is your backup
Web /etc/pki/ca-trust/source/anchors/joyce310-ca.pem Optional — makes this machine trust your CA
Three paths that sound alike
Path What it is
/etc/pki/CA/cacert.pem Your CA's public certificate. Safe to distribute; this is what clients need in order to trust certificates you sign
/etc/pki/CA/private/cakey.pem Your CA's private key. Never leaves Host 2
/etc/pki/tls/certs/ca-bundle.crt Rocky's bundle of public trusted roots, regenerated by update-ca-trust. Unrelated to your CA
Syntax decoder
OpenSSL subcommands
Subcommand What it does
genrsa Generates an RSA keypair; the private key file contains the public key too
req Certificate request utility — creates CSRs, or self-signed certs with -x509
ca The signing engine; reads openssl.cnf, checks policy, consumes a serial, records the issue
x509 Inspects or manipulates an existing certificate
rsa Inspects or converts a key; also strips or adds passphrase encryption
verify Checks a certificate against a trust anchor
s_client Opens a TLS connection as a client, for testing
Flags used
Flag Meaning
-aes256 Encrypt the key file with AES-256; triggers the passphrase prompt
-out Destination file
-in Input file
-key Use an existing key rather than generating one
-new Build a new request interactively
-x509 Self-sign and emit a certificate instead of a request
-days N Validity period
-noout Suppress the base64 blob
-text Print the human-readable decode
-subject, -issuer, -dates Print just those fields
-modulus Print the key modulus — identical for a matching key and certificate
-check Validate a key, which requires decrypting it
-CAfile Trust anchor to verify against
-extfile Extensions file, used to add a SAN at signing time
-servername Set the SNI field manually
-config Point at a specific openssl.cnf
Shell and system
Command Meaning
mkdir -p a/{b,c} Brace expansion into separate paths; -p makes parents and ignores existing
chmod 600 / 644 / 700 Owner-only read-write / world-readable / owner-only directory
restorecon -Rv Reset SELinux labels recursively, verbosely
ls -Z Show SELinux context
scp src user@host:/dst/ Secure copy over SSH; the colon marks the remote path
ss -lntp Listening, numeric, TCP, with process — -p needs root
firewall-cmd --permanent Write to disk only; needs --reload to take effect
httpd -M List loaded Apache modules
apachectl configtest Check config syntax without restarting
journalctl -xeu httpd Service logs, explained, end-of-file, this unit
2>/dev/null Discard stderr — useful with find across /
curl
Flag Meaning
-k / --insecure Skip certificate verification entirely
--cacert FILE Use this trust anchor instead of the system bundle
--resolve HOST:PORT:IP Fake DNS entry for this command only
--connect-to HOST:PORT:HOST2:PORT2 Same idea, but the target is a hostname
-v Verbose — shows the handshake and verification result
Troubleshooting index
Message or symptom Cause Fix
countryName field is different between CA certificate and the request Phase 3 and Phase 4 subject answers diverged Regenerate the CSR with matching Country, State, Organization. The key is fine
unable to load CA private key / bad decrypt Wrong password, or openssl ca is looking elsewhere Check dir under [ CA_default ] in /etc/pki/tls/openssl.cnf
TXT_DB error number 2 A certificate with that exact subject already exists in index.txt echo "unique_subject = no" > /etc/pki/CA/index.txt.attr, or revoke the old one
I/O error on serial or index.txt File missing or empty Redo Phase 1
Permission denied (publickey,gssapi-keyex,...) with no password prompt PermitRootLogin prohibit-password is the Rocky 9 default Copy via a normal user to /tmp, or set PermitRootLogin yes and restart sshd
Permission denied, please try again after a prompt Genuine auth failure, or root has no password passwd -S root — P means set, NP/LK means not usable
scp: /etc/pki/CA/websrv.csr: Permission denied after login succeeds Destination directory missing or wrong ownership ls -ld /etc/pki/CA, then chown/chmod/restorecon
Connection refused sshd not running on the far side systemctl enable --now sshd
No route to host / timeout Firewall or wrong subnet firewall-cmd --list-all; look for ssh
Could not resolve hostname DNS Use the raw IP, or add entries to /etc/hosts on both machines
Host key warning about a changed key VM was rebuilt ssh-keygen -R 192.168.1.20
bash: scp: command not found on the remote Minimal install dnf install openssh-clients; scp needs the binary on both ends
websrv.crt not in /root Signing aborted, transfer failed, or you looked without sudo Check index.txt and newcerts/, then sudo ls -la /root/
SSLCertificateFile: file does not exist or is empty Typo in path, or the certificate never reached /etc/pki/tls/certs/ Verify the path in ssl.conf against ls -l
Permission denied on the key, from Apache SELinux label, often user_tmp_t from a /tmp hop restorecon -Rv /etc/pki/tls/, then ausearch -m avc -ts recent
unable to check private key Certificate and key are not a pair Compare moduli with the openssl md5 pipeline
httpd fails to start, log mentions bad decrypt Encrypted key with nowhere to prompt Strip the password with openssl rsa, or configure SSLPassPhraseDialog
Address already in use Something else holds 443 ss -lntp | grep :443
curl: (60) with "unable to get local issuer certificate" CA not in the trust store Install cacert.pem into source/anchors/ and run update-ca-trust
curl: (60) with a name mismatch CN is Joyce310, URL says something else --resolve, /etc/hosts, or reissue with a SAN
curl: (7) Failed to connect ... port 443 Nothing listening ss -lntp | grep :443 and systemctl status httpd
Browser warns even after trust store install No SAN in the certificate Reissue with -extfile and a subjectAltName block
First move for anything Apache-related
journalctl -xeu httpd --no-pager | tail -40