CLI reference
pqdoctor <host[:port]> [options] check one server ([v6addr]:port for IPv6)pqdoctor scan <file|-> [options] check a list (one host per line, host:port, https:// URLs or Tranco 'rank,domain' CSV)pqdoctor ssh <host[:port]> an SSH server's key exchange (port 22; also ssh://[user@]host[:port])pqdoctor quic <host[:port]> the key exchange over QUIC v1 (UDP, port 443)pqdoctor dtls <host:port> DTLS 1.3 / 1.2 over UDPpqdoctor diff <old.jsonl> <new.jsonl> what got fixed, regressed or changedpqdoctor fix --config PATH [--stack S] [--write]pqdoctor completions bash|zsh|fish|powershellpqdoctor man the manual page (roff)pqdoctor --help | -hpqdoctor --version | -Vpqdoctor <host>
Section titled “pqdoctor <host>”Checks one server: the seven key-exchange probes, the group survey, the certificate check with trust, and every address the name resolves to.
pqdoctor github.com # report on screenpqdoctor example.org:8443 --json # the full report as JSONpqdoctor smtp.example.com:587 # STARTTLS first, picked by portpqdoctor db.example.com:5432 # PostgreSQL's SSLRequest firstpqdoctor [2001:db8::1]:443 # an IPv6 addresspqdoctor 203.0.113.10 --no-http # an IP: no SNI, no Server headerpqdoctor example.org --audit --middlebox --quic| Option | Default | |
|---|---|---|
--port N, -p N |
443 |
Port for a host given without one |
--format F, -f F |
text |
text, json, csv, html, sarif or junit (formats). 0.2.0 |
--json |
The same as --format json |
|
--no-http |
Don’t fetch the HTTP Server header on port 80. Fixes are then generic |
|
--no-auth |
Skip the certificate / ML-DSA check (and so trust) | |
--no-trust |
Don’t check the chain against Mozilla’s roots. 0.2.0 | |
--no-groups |
Skip the post-quantum group survey (5 connections). 0.2.0 | |
--slh-dsa |
off | One more certificate probe offering only SLH-DSA. 0.2.0 |
--audit |
off | TLS versions, TLS 1.2 suites, weak features, about 60 connections. 0.2.0 |
--middlebox |
off | Split, fragmented and resized hellos, 8 connections. 0.2.0 |
--quic |
off | Also the QUIC check on the same port. 0.2.0 |
--dtls |
off | Also DTLS on the same UDP port. 0.2.0 |
--proxy URL |
Every TCP connection through a proxy. 0.2.0 | |
--starttls P |
by port | smtp, imap, pop3, ftp, xmpp, ldap, postgres, mysql, rdp, auto or none (STARTTLS) |
--first-address |
Only the first address. By default up to 6 are checked and listed when they differ | |
--user-agent S |
pqdoctor/0.2.0 (post-quantum TLS check; handshake-only) |
User-Agent for the header request |
--contact S |
Added to the User-Agent, e.g. an email or URL for opt-outs | |
--cbom FILE |
Write a CycloneDX 1.6 CBOM. 0.2.0 | |
--history FILE |
Append a record to FILE. 0.2.0 | |
--no-config |
Ignore the config file. 0.2.0 |
STARTTLS by port: 25 and 587 SMTP, 143 IMAP, 110 POP3, and from 0.2.0 21 FTP, 5222 XMPP, 389 LDAP, 5432 PostgreSQL, 3306 MySQL, 3389 RDP.
Exit status: 0 post-quantum, 1 not post-quantum, 2 unreachable or an error.
pqdoctor scan
Section titled “pqdoctor scan”Checks a list. See Scanning a list for a walkthrough.
pqdoctor scan hosts.txtpqdoctor scan hosts.txt --json --out results.jsonl --resume --contact you@example.orgpqdoctor scan hosts.txt --format sarif > pq.sarifcat hosts.txt | pqdoctor scan - --quietInput: one target per line. Accepted forms: host, host:port, [v6addr]:port, an https:// URL (host and port taken from it), or a Tranco rank,domain CSV line (the last field is used). Lines that don’t parse are counted as invalid and skipped.
| Option | Default | |
|---|---|---|
--port N |
443 |
Port for targets without one |
--format F |
text |
text (TSV) and json (JSON Lines) and csv stream a line per host; html, sarif and junit print one document at the end |
--out FILE |
Also append JSON records to FILE | |
--resume |
Skip targets already in --out (matched on host and port) |
|
--concurrency N, -c N |
4 |
Hosts in flight at once, max 32 |
--rate R |
2 |
New hosts started per second across all workers, max 20 |
--limit N |
Only the first N targets | |
--exclude FILE |
Hosts and domains not to contact. A domain covers its subdomains | |
--skip-ranges FILE |
Networks not to probe, as provider cidr lines. Recorded as skipped |
|
--www-fallback |
Try www.<domain> when the domain has no address |
|
--resolve-only |
Resolve and filter only. Contacts no host | |
--auth |
off | Also run the certificate check (2 more connections a host) |
--groups |
off | Also the group survey (5 more connections a host). 0.2.0 |
--all-addresses |
off | Every address of each host (up to 6), not just the first. 0.2.0 |
--audit, --middlebox, --quic, --dtls, --proxy |
off | As for one host. 0.2.0 |
--cbom FILE, --history FILE |
As for one host, for every host. 0.2.0 | |
--no-http, --user-agent, --contact, --starttls |
As for one host | |
--quiet, -q |
No progress or summary on stderr |
Each host’s probes stay one at a time, whatever the concurrency. With --all-addresses, --skip-ranges is checked against the first address only.
Exit status: 0 when the scan completes, whatever the verdicts. 2 when an input file can’t be read, --out can’t be opened, or --skip-ranges has no ranges. To fail on results, read the output (examples), or use --format junit / sarif in CI.
pqdoctor ssh
Section titled “pqdoctor ssh”Checks an SSH server’s key exchange offer, and flags post-quantum host-key algorithms from drafts. See SSH.
pqdoctor ssh github.compqdoctor ssh git.example.com:2222 --jsonpqdoctor ssh ssh://git@example.org:7999 # the user name is ignoredTakes --format, --proxy, --history and --cbom. Exit status: 0 post-quantum, 1 draft-only or not post-quantum, 2 unreachable or an error.
pqdoctor quic / dtls
Section titled “pqdoctor quic / dtls”0.2.0. The key exchange over UDP. See QUIC and DTLS.
pqdoctor quic cloudflare.com # port 443pqdoctor dtls vpn.example.org:4433 --jsonNeither can go through --proxy (HTTP CONNECT and SOCKS5 CONNECT carry TCP only); with one set they exit 2 and say so. Exit status as for one host.
pqdoctor diff
Section titled “pqdoctor diff”0.2.0. Compares two history files: the newest record per target in each.
pqdoctor diff last-week.jsonl today.jsonlpqdoctor diff old.jsonl new.jsonl --jsonA target is fixed when it went from not post-quantum to post-quantum, regressed when it went the other way (and still answers), changed when its verdict, cause or group changed otherwise, and added / removed when it’s in only one file. Exit status: 1 when something regressed, else 0; 2 when a file can’t be read. See History, diff and CBOM.
pqdoctor fix
Section titled “pqdoctor fix”0.2.0. Puts the post-quantum group (or key exchange) first in a server’s config. See Fixing a config.
pqdoctor fix --config /etc/nginx/nginx.conf # a unified diff; nothing changespqdoctor fix --config /etc/nginx/nginx.conf --write # writes it, keeps nginx.conf.bakpqdoctor fix --config /etc/ssh/sshd_config --write --host git.example.org| Option | |
|---|---|
--config PATH |
The file. Required |
--stack S |
nginx, apache, haproxy, postfix, dovecot, sshd, caddy or iis, when pqdoctor can’t tell from the file |
--write |
Write the change, keeping the original as PATH.bak (or .bak.1, …), and print the test, reload and re-check commands |
--host H |
The server to put in the printed re-check command |
It never reloads anything. Caddy and IIS have nothing to rewrite; for them it explains what to do instead.
pqdoctor completions / man
Section titled “pqdoctor completions / man”0.2.0.
pqdoctor completions bash > /etc/bash_completion.d/pqdoctorpqdoctor completions zsh > "${fpath[1]}/_pqdoctor"pqdoctor completions fish > ~/.config/fish/completions/pqdoctor.fishpqdoctor completions powershell >> $PROFILEpqdoctor man > /usr/local/share/man/man1/pqdoctor.1Config file
Section titled “Config file”0.2.0. Long options as key = value lines, read before the command line, so the command line wins. true turns a flag on, false leaves it out.
# %APPDATA%\pqdoctor\config on Windows; $XDG_CONFIG_HOME/pqdoctor/config if setcontact = security@example.orgproxy = socks5h://127.0.0.1:1080no-http = truerate = 1PQDOCTOR_CONFIG names another file; --no-config skips it. A line that isn’t key = value exits 64, naming the line.
Output formats
Section titled “Output formats”| Format | One host | Scan |
|---|---|---|
text |
The report as a terminal reads it | TSV, a line per host |
json |
The report, pretty-printed | A record per line |
csv |
Header + one row | Header + a row per host. Cells starting with = + - @ get a leading ' so spreadsheets don’t run them |
html |
The full report, one file | A summary and a row per host |
sarif |
SARIF 2.1.0: a result per host, a rule per cause code | The same. Locations point at the host list |
junit |
JUnit XML: post-quantum passes, not post-quantum fails, unreachable is an error | The same, skipped hosts skipped |
HTML is self-contained: inline CSS, no scripts, no external requests, a Content-Security-Policy that allows nothing else.
Output streams
Section titled “Output streams”| Stream | Gets |
|---|---|
| stdout | The report or the scan output |
| stderr | Errors, and a scan’s start line, progress and summary |
Colour is used only when stdout is a terminal and NO_COLOR isn’t set, so piping and redirecting give plain text.
Scan TSV
Section titled “Scan TSV”host port verdict cause group stack headlinegithub.com 443 notPq no-mlkem - - Not post-quantum- stands for an empty group or stack. Tabs in a headline become spaces. The header is left out when appending to an existing --out with --resume.
Scan summary (stderr)
Section titled “Scan summary (stderr)”pqdoctor: 3 targets (0 invalid lines, 0 excluded, 0 already done) · 3 at a time · ≤2 new/spqdoctor: 3 targets, 0 skipped, 3 answered TLS, 1 post-quantum (33.3% of answered) no-mlkem 1 pq-preferred 1 tls12-only 1“Answered TLS” leaves out unreachable, error, skipped and resolved records. Causes are sorted by count.
Exit status
Section titled “Exit status”| Code | One host, ssh, quic, dtls |
scan |
diff |
|---|---|---|---|
0 |
Post-quantum | Completed | Nothing regressed |
1 |
Not post-quantum, TLS 1.2 only, broken, or draft-only | — | Something regressed |
2 |
Unreachable, or an error | Couldn’t read input or write output | Couldn’t read a file |
64 |
Bad arguments or config file | Bad arguments | Bad arguments |
On bad arguments pqdoctor prints the problem and Run 'pqdoctor --help' for usage. to stderr.
Version
Section titled “Version”pqdoctor --version # pqdoctor 0.2.0