Hydra
Hydra throws username and password combinations at a live login service (SSH, FTP, an HTTP form, a database, SMB) and tells you which pair the service accepts. Reach for it once you have found a login and a sensible user and password list, to answer one narrow question: does this service take weak or reused credentials? Every try is a real, logged authentication attempt, so scope, rate, and lockout awareness are the whole game.
Mental model: hydra [-l user | -L users] [-p pass | -P passes] service://TARGET. Everything else tunes who, how fast, and when to stop.
New to online login testing? Core concepts
- Login (
-l/-L): one username, or a file of them - Password (
-p/-P): one password, or a wordlist;-Creads auser:passcombo file instead - Service module: the protocol you attack (
ssh,ftp,http-post-form, …), chosen byservice://TARGETor a trailing module name - Tasks (
-t): parallel connections; default is 16, lower is gentler and lockout-safer - Condition string: for web forms, an
F=(fail) orS=(success) marker Hydra matches in the response to decide a hit - Placeholders
^USER^/^PASS^: where Hydra substitutes each candidate inside a form body - Stop on success (
-f/-F): quit after the first valid pair for this host / for any host - Lockout: too many failures can disable an account; low
-tand spraying avoid it
When to reach for Hydra (and when not)
Reach for it to prove a live service accepts weak or reused credentials: a deliberately weak CTF SSH/FTP/web login, a password-policy or lockout gap on an authorized assessment, or a defensive check that your own lockout and alerting actually fire.
Reach for something else when you already have password hashes (crack them offline with john or hashcat, faster, silent, no lockouts), when a web login uses CSRF tokens or a multi-step flow (Burp Intruder handles per-request tokens), when you are spraying Active Directory at scale (netexec / crackmapexec), or when you only need to test one or two known creds (just log in).
Install, update, verify#
sudo apt install -y hydra # Debian / Ubuntu / Kali
sudo dnf install -y hydra # Fedora / RHEL
sudo apt install --only-upgrade -y hydra # update
hydra -h | less # options, plus the compiled-in service list at the bottom
command -v hydra # confirm it is on PATH
hydra -U http-post-form # usage help for one module
Kali ships Hydra. The package includes the common protocol modules; build from source only if you need a module your distro left out. seclists (sudo apt install -y seclists) gives you sane user and password lists to point it at.
Flags you’ll actually use#
Skim these first; the cheat sheet below is these flags combined.
| Flag | Does | Flag | Does |
|---|---|---|---|
-l / -L | Single user / user-list file | -s | Set a non-default port |
-p / -P | Single password / password-list file | -t | Parallel tasks per target (default 16) |
-C | Colon combo file user:pass | -T | Parallel tasks across all targets (-M) |
-e nsr | Also try empty, login-as-pass, reversed | -w / -W | Response timeout / delay between tries |
-x MIN:MAX:CHARS | Generate passwords instead of a list | -f / -F | Stop on first hit (this host / any host) |
-u | Loop users per password (spray order) | -o / -b | Save output / output format (json) |
-M | Target-list file (many hosts) | -V / -d | Show each attempt / debug |
-S | Connect with SSL/TLS | -R | Restore an aborted session |
Cheat sheet#
Every block starts simple and adds one flag at a time, so you can stop at the line that does the job.
# Build up an SSH test: stop at the line you need
hydra -l admin -P PASSLIST ssh://TARGET_IP # 1. all passwords for admin
hydra -l admin -P PASSLIST ssh://TARGET_IP -t 4 # 2. + slow to 4 tasks
hydra -l admin -P PASSLIST ssh://TARGET_IP -t 4 -f # 3. + stop at first valid pair
hydra -l admin -P PASSLIST ssh://TARGET_IP -t 4 -f -V # 4. + print every attempt
hydra -l admin -P PASSLIST ssh://TARGET_IP -t 4 -f -o hits.txt # 5. + save hits to a file
# Pick who and what to try
hydra -l admin -P PASSLIST ssh://TARGET_IP # one user, a password list
hydra -L USERLIST -p 'Winter2026!' ssh://TARGET_IP # many users, one password (spray)
hydra -L USERLIST -P PASSLIST ssh://TARGET_IP # every user x every password
hydra -C combos.txt ssh://TARGET_IP # a user:pass combo file
hydra -l admin -P PASSLIST -e nsr ssh://TARGET_IP # also try empty, login-as-pass, reversed
hydra -l admin -x 4:6:a1 ssh://TARGET_IP # generate passwords, no list
# Common services (swap the module, keep the flags)
hydra -L USERLIST -P PASSLIST ftp://TARGET_IP
hydra -L USERLIST -P PASSLIST smb://TARGET_IP
hydra -l administrator -P PASSLIST rdp://TARGET_IP
hydra -l root -P PASSLIST mysql://TARGET_IP
hydra -l postgres -P PASSLIST postgres://TARGET_IP
hydra -l admin -P PASSLIST telnet://TARGET_IP
hydra -P PASSLIST vnc://TARGET_IP # VNC often has no username
hydra -P community.txt snmp://TARGET_IP # brute SNMP community strings
# HTTP logins
hydra -L USERLIST -P PASSLIST TARGET_IP http-get /protected/ # HTTP basic auth
hydra -l admin -P PASSLIST TARGET_IP http-post-form \
"/login.php:user=^USER^&pass=^PASS^:F=Login failed" # form, fail marker
hydra -l admin -P PASSLIST TARGET_IP http-post-form \
"/login:user=^USER^&pass=^PASS^:S=Logout" # form, success marker
hydra -l admin -P PASSLIST TARGET_IP http-post-form \
"/login:user=^USER^&pass=^PASS^:C=/login:F=Invalid" # grab a cookie first
hydra -l admin -P PASSLIST TARGET_IP https-post-form \
"/login:user=^USER^&pass=^PASS^:F=Invalid" # over TLS
# Rate and lockout control
hydra -l admin -P PASSLIST ssh://TARGET_IP -t 4 # fewer tasks (default 16 is aggressive)
hydra -L USERLIST -p 'Winter2026!' ssh://TARGET_IP -t 1 -u # one at a time, spray order
hydra -l admin -P PASSLIST ssh://TARGET_IP -W 3 # wait 3s between attempts per task
hydra -l admin -P PASSLIST ssh://TARGET_IP -w 10 # allow 10s per response (slow service)
hydra -l admin -P PASSLIST ssh://TARGET_IP -s 2222 # non-default port
# Output, sessions, many hosts
hydra -l admin -P PASSLIST ssh://TARGET_IP -o hits.txt # write valid pairs to a file
hydra -l admin -P PASSLIST ssh://TARGET_IP -o hits.json -b json # ... as JSON
hydra -L USERLIST -P PASSLIST -M targets.txt ssh -T 8 # many targets, 8 tasks total
hydra -R # resume the last aborted run
Command breakdowns#
Each block below explains one situation, the exact command, and what to expect back.
Test SSH for a weak password#
hydra -l admin -P /usr/share/seclists/Passwords/Common-Credentials/10k-most-common.txt ssh://TARGET_IP -t 4 -f -o notes/ssh.txt
Tries each listed password for admin. -f stops at the first valid pair, -t 4 keeps it slow enough to dodge lockouts, -o saves the hit. A green login: admin password: ... line is a candidate; 0 valid passwords found is the normal miss.
Spray one password across many users (lockout-safe)#
hydra -L users.txt -p 'Winter2026!' ssh://TARGET_IP -t 1 -u
One likely password against every user. -u loops users per password so you never stack failures on one account, and -t 1 sends them one at a time. This is the gentlest thing to run against lockout policies.
Try empty, username-as-password, and reversed logins#
hydra -l admin -P passwords-small.txt -e nsr ssh://TARGET_IP -t 4
-e nsr adds three cheap extra guesses per user on top of the list: n empty password, s login used as the password, r the login reversed. Common wins on lab and default accounts.
Brute-force an FTP login#
hydra -L users.txt -P passwords-small.txt ftp://TARGET_IP -t 4 -V
-V prints every [ATTEMPT] line so you can confirm the module is actually talking to the service. Swap ftp for smb, telnet, pop3, imap, or smtp with the same flag layout.
Brute-force an HTTP POST login form#
hydra -l admin -P passwords-small.txt TARGET_IP http-post-form \
"/login.php:username=^USER^&password=^PASS^:F=Login failed" -t 4
Three colon-separated fields: the path, the POST body with ^USER^ / ^PASS^ placeholders, and the failure marker. F=Login failed means “this attempt failed if that string is in the response.” Capture a real failed login in Burp first to get the exact field names.
Match on a success string instead of a failure string#
hydra -l admin -P pw.txt TARGET_IP http-post-form \
"/login:user=^USER^&pass=^PASS^:S=Logout" -t 4
S= flips the logic: a hit is when the marker appears. Pick a string that only shows up after login (a Logout link, a dashboard heading). Use S or F, whichever is reliably present on exactly one outcome.
Send a cookie or header with the form request#
hydra -l admin -P pw.txt TARGET_IP http-post-form \
"/login:user=^USER^&pass=^PASS^:C=/login:H=X-Requested-With\: XMLHttpRequest:F=Invalid"
C=/login fetches that page first to pick up a fresh session cookie, and H= adds a header (here the AJAX header some apps demand). Append more H= fields as needed. This does not solve per-request CSRF tokens.
Crack HTTP basic authentication#
hydra -L users.txt -P pw.txt TARGET_IP http-get /protected/ -t 4
The http-get module handles the browser 401 / WWW-Authenticate popup, not a form. Point it at the protected path. Use https-get for a TLS site.
Test a login over HTTPS#
hydra -l admin -P pw.txt TARGET_IP https-post-form \
"/login:user=^USER^&pass=^PASS^:F=Invalid"
The https- module variants speak TLS, so you do not need -S. Reach for -S only when you are forcing SSL onto a plain module like ftp or smtp.
Target a service on a non-standard port#
hydra -l admin -P pw.txt ssh://TARGET_IP -s 2222
-s overrides the module’s default port. Confirm the real port with Nmap first; a wrong port shows up as instant connection errors, not misses.
Test SMB or RDP credentials#
hydra -L users.txt -P pw.txt smb://TARGET_IP -t 1
hydra -l administrator -P pw.txt rdp://TARGET_IP -t 1
Keep -t 1 on Windows auth: SMB and RDP lock accounts fast and generate loud failed-logon events. For broad AD spraying, netexec is the better tool.
Test a database login (MySQL, Postgres)#
hydra -l root -P pw.txt mysql://TARGET_IP
hydra -l postgres -P pw.txt postgres://TARGET_IP
Database modules brute the DB account directly. root (MySQL) and postgres (Postgres) are the usual default supers. mssql works the same way for SQL Server.
Use a colon-separated user:pass combo list#
hydra -C combos.txt ssh://TARGET_IP -t 4
-C reads one user:pass per line and tries them as fixed pairs, instead of the full -L x -P product. Use it for leaked-credential lists where the pairing matters.
Generate passwords on the fly instead of a wordlist#
hydra -l admin -x 4:6:aA1 ssh://TARGET_IP -t 4
-x MIN:MAX:CHARSET builds passwords itself (a lowercase, A upper, 1 digits, or literal chars). Here: length 4 to 6, mixed alnum. It replaces -p / -P and blows up fast, so keep the length and charset tight.
Save hits and resume an interrupted run#
hydra -l admin -P pw.txt ssh://TARGET_IP -t 4 -o "notes/hydra-$(date +%Y%m%d-%H%M).txt"
hydra -R # after a crash or Ctrl-C, resume from ./hydra.restore
-o keeps timestamped evidence. If a run dies, Hydra leaves a hydra.restore file in the working directory; hydra -R picks it back up (run it from the same directory).
Reading the output#
[22][ssh] host: TARGET_IP login: admin password: hunter2- a candidate hit (port, module, then the pair). Always confirm with a real manual login before you trust or report it.0 valid passwords found- nothing on the list worked at this rate. A normal, common result, not an error.[ATTEMPT] ... - N of M(with-V) - live progress; theN of Mcount tells you how far a run has to go.[ERROR] ... could not connectrepeated - wrong port or service, the target rate-limited you, or the service is down. Not the same as a clean miss.- Every attempt reads as a hit, or none ever do - your
F=/S=marker is wrong. Fix the marker, do not trust the run.
Troubleshooting#
| Problem | Cause | Fix |
|---|---|---|
| Every attempt “valid” | Marker matches on all responses | Pick an F= / S= string unique to one outcome |
| All attempts fail instantly | Wrong path or field names | Recheck the POST body and URL against a captured request |
could not connect | Wrong port or service | Confirm with Nmap; set -s PORT |
| Account lockouts | -t too high | Drop to -t 1 / -t 2, add -u, spray instead |
| Very slow | Giant list or slow service | Trim the list; raise -t cautiously; -w for timeouts |
Could not resolve / no route | Bad host or out of scope | Verify IP, route, and firewall/scope |
| Module not found | Not compiled into the package | Use a build that includes it; hydra -h lists what you have |
Gotchas#
- The default
-t 16is aggressive. Against SSH, SMB, RDP, or any lockout-protected login, drop it to-t 4or lower before you disable an account and blow your scope. F=/S=is a plain substring match on the response body. If your marker also appears on the login page itself (the word “Login”), detection is meaningless. Choose a string present on exactly one outcome.-fis per-host and finishes in-flight tasks. You may see a couple of extra attempts after the hit, and across multiple targets (-M) you need-Fto stop everywhere.- CSRF tokens and one-time nonces defeat a static form string. Hydra cannot parse a fresh token per request;
C=only handles cookies. Use Burp Intruder or a script when the body carries a per-request token. - A “hit” is only the condition matching, not a proven session. A wrong marker gives false positives and false negatives alike, so a manual login is mandatory before you believe it.
- Attempt counts multiply.
-L usersx-P passx-e sgrows quickly; a 100-user, 10k-password run is a million real, logged authentication events.
Pairs with#
nmap finds which login services are open and on what ports before you point Hydra at one. When you already hold hashes, john and hashcat crack them offline: faster, silent, and no lockouts, so that is the right call whenever hashes exist. seclists supplies the curated user and password lists, and Burp or the browser dev tools capture a real login request so your http-post-form fields and markers are exact. For spraying Active Directory or complex token-driven web logins, netexec and Burp Intruder go where Hydra cannot; medusa and ncrack are close alternatives with different module coverage.