One of the most common questions that I see in my favorite IRC channel is: "How can I secure sshd on my server?" There's no single right answer, but most systems administrators combine multiple techniques to provide as much security as possible with the least inconvenience to the end user.
Here are my favorite techniques listed from most effective to least effective:
SSH key pairs
By disabling password-based authentication and requiring ssh key pairs, you reduce the chances of compromise via a brute force attack. This can also help you protect against weak account passwords since a valid private key is required to gain access to the server. However, a weak account password is still a big problem if you allow your users to use sudo.
If you're new to using ssh keys, there are many great guides that can walk you through the process.
Firewall
Limiting the source IP addresses that can access your server on port 22 is simple and effective. However, if you travel on vacation often or your home IP address changes frequently, this may not be a convenient way to limit access. Acquiring a server with trusted access through your firewall would make this method easier to use, but you'd need to consider the security of that server as well.
The iptables rules would look something like this:
iptables -A INPUT -j ACCEPT -p tcp --dport 22 -s 10.0.0.20 iptables -A INPUT -j ACCEPT -p tcp --dport 22 -s 10.0.0.25 iptables -A INPUT -j DROP -p tcp --dport 22
Use a non-standard port
I'm not a big fan of security through obscurity and it doesn't work well for ssh. If someone is simply scanning a subnet to find ssh daemons, you might not be seen the first time. However, if someone is targeting you specifically, changing the ssh port doesn't help at all. They'll find your ssh banner quickly and begin their attack.
If you prefer this method, simply adjust the Port configuration parameter in your sshd_config file.
Limit users and groups
If you have only certain users and groups who need ssh access to your server, setting user or group limits can help increase security. Consider a server which needs ssh access for developers and a manager. Adding this to to your sshd_config would allow only those users and groups to access your ssh daemon:
AllowGroups developers AllowUsers jsmith pjohnson asamuels
Keep in mind that any users or groups not included in the sshd_config won't be able to access your ssh server.
TCP wrappers
While TCP wrappers are tried and true, I consider them to be a bit old-fashioned. I've found that many new systems administrators may not think of TCP wrappers when they diagnose server issues and this could possibly cause delays when adjustments need to be made later.
If you're ready to use TCP wrappers to limit ssh connections, check out Red Hat's extensive documentation.
fail2ban and denyhosts
For those systems administrators who want to take a bit more active stance on blocking brute force attacks, there's always fail2ban or denyhosts. Both fail2ban and denyhosts monitor your authentication logs for repeated failures, but denyhosts can only work with your ssh daemon. You can use fail2ban with other applications like web servers and FTP servers.
The only downside of using these applications is that if a valid user accidentally tries to authenticate unsuccessfully multiple times, they may be locked out for a period of time. This could be a big problem if you're in the middle of a server emergency.
A quick search on Google will give you instructions on fail2ban configuration as well as denyhosts configuration.
Port knocking
Although port knocking is another tried and true method to prevent unauthorized access, it can be annoying to use unless you have users who are willing to jump through additional hoops. Port knocking involves a "knock" on an arbitrary port that then allows the ssh daemon to be exposed to the user who sent the original knock.
Linux Journal has a great article explaining how port knocking works and it provides some sample configurations as well.
Conclusion
The best way to secure your ssh daemon is to apply more than one of these methods to your servers. Weighing security versus convenience of access isn't an easy task and it will be different for every environment. Regardless of the method or methods you choose, ensure that the rest of your team is comfortable with the changes and capable of adapting to them efficiently.

The first thing that you should always do upon setting up a new server (especially a CentOS / Red Hat server, as those are notorious for leaving this on by default) is to set:
PermitRootLogin false
in your sshd_config - at a minimum, this will at least prevent your server from succumbing to the nastiest and simplest attack - someone guessing your root password and logging in.
Thanks, Jason! I knew I was missing something.
You can also limit access by requiring authentication through a VPN first. There are also cheap two-factor authentication methods like Yubikey.
I do agree that it takes a multi-layered approach, like anything with security. Good article!
I had another thought on key-based authentication. It is most often good practice to use the "from=xxx.xxx.xxx.xxx" in the authorized_keys file of the server, replacing the x characters with the IP address of the connecting client. Although that again is a convenience breaker, if someone steals your private key or guesses its password, you wont need to worry about anyone logging in from an unauthorized computer. Enjoyed this article, port knocking sounds the most cumbersome to me personally.
Hey Ryan -
Good point on the IP limitation with the ssh keys. Also, I agree - port knocking seems to be the least convenient option.
Great points. Here are some other steps you can take.
Listen:
Set the Listen directive to only Listen to a single IP. This is not so much as to hide your SSH-bound IPs but I've seen aggressive brute force attacks hit ranges of IPs. As a result, those attacks hit several IPs on your server at once, overloading SSH's ability to accept connections.
MaxAuthTries
I also change the MaxAuthTries to 3 from the default of 6. During brute force attacks, this will require more TCP/IP connections rather than streaming 6 attempts in one connection. Trivial I know but another layer. Also, I need to double check if this is still valid. On a recent test, I found it did not work as expected.
LoginGraceTime
This is the time someone has to enter a password. I usually lower this to 1m or even 30s from the default of 2m.
Banning Tools:
You have to be careful with DenyHosts, fail2ban, and similar tools, such as the bfd module for the popular APF firewall. Many of these tools have been shown subject to log injection attacks. They are simply using regular expressions to look at the logs. If someone can trick that regular expression, they can use telnet and some spoofing to get IPs blacklisted. I think fail2ban patched this I am not sure about DenyHosts.
I currently use rate limiting IPtables rules for SSH. I've found this very effective on shared hosting systems that seem to get attacked frequently and do not permit you to completely block ssh.
Port Knocking
I think this may have gone out of favor, but it is a curious technology.
Good points, Jeff! The listen directive can be really handy if your servers share a private network. You can use one server as a bastion to reach the others.
I had a iptables rule that dropped packets from a host that issued n connects per second to a port (e.g. if 5 connections in the past 3 seconds to ssh port, drop for a minute) - look up ipt_recent
moving from port 22 to port 2222 took the number of brute force attempts from many to ZERO.
I'm a pretty big fan of PPP-pam to implement perfect paper passwords.It's like the poor mans SecurID.
http://code.google.com/p/ppp-pam/
https://www.grc.com/ppp.htm
I'm actually a big fan of using a non-standard port. As I mentioned in http://pl.atyp.us/wordpress/?p=2592, port scans in public clouds seem to be pretty common, and the same is likely true for address space allocated to more traditional hosting providers. The example is from Rackspace BTW. I consider use of a non-standard ssh port to be a necessary part of securing a server, even though you're also correct to point out that it's far from sufficient by itself.
For those who want more security than that, I like the port-knocking approach. Sure, it's a tad inconvenient, but if you do it right then practically the only way somebody's going to get past it is to be watching very carefully while you log in yourself - and you should already be careful not to let anyone observe you so closely. A small variant that I've also seen used is to have the "knock" be an OTP so that even duplicating your actions won't suffice.
Jeff -
Thanks for the comment. I'd agree with you on moving the ssh port. Keeping it off 22 is a great idea, but that has to be just part of your security plan.
Also, for everyone else reading this comment, Jeff Darcy's blog is a great wealth of information: http://pl.atyp.us/
I use iptables to limit the number of connection attempts per minute as well though it can sometimes be a pain when uploading a lot of files via Transmit or some application that makes several connections.
iptables -A INPUT -p tcp -m state --state NEW --dport 22 -m recent --name sshattack --set
iptables -A INPUT -m recent --name sshattack --rcheck --seconds 60 --hitcount 4 -m limit --limit 4/minute -j LOG --log-prefix 'SSH attack: '
iptables -A INPUT -m recent --name sshattack --rcheck --seconds 60 --hitcount 4 -j DROP
iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
That'll block any host that fails ssh authentication 3 times in a row. They're only blocked out for 3 minutes. This isn't full proof by any means, but is usually enough to foil the bruteforce script kiddies that would never get in anyway.
Why use "denyhosts" or these other wrapper programs that parse syslog. Use hashlimits instead, it keeps all of your firewall rules in the right place and teaches people to actually use their system:
"hashlimit uses hash buckets to express a rate limiting match (like the limit match) for a group of connections using a single iptables rule. Grouping can be done per-hostgroup (source and/or destination address) and/or per-port. It gives you the ability to express 'N packets per time quantum per group'"
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j SSH
-A SSH -m hashlimit --hashlimit 2/minute --hashlimit-mode srcip --hashlimit-burst 1 --hashlimit-htable-expire 900000 --hashlimit-name ssh -j ACCEPT
Connect more than twice in a minute, you get blocked for 15.
Additionally, I feel that recommending tcp_wrappers is just criminal these days. It can lead to a false sense of security because applications actually have to written to support it, and then enabled when compiled. Because the amount of software that supports tcp_wrappers is so few these days its not going to help much.
If you're busy out and about traveling can you limit the mac addresses that have access? presumably you'll mostly be using the same mobile devices to ssh into your server.
Mark - your server will only see the MAC address of your gateway device in the datacenter, so limiting it by MAC won't work.
Frequent travelers might need access to a bastion host that they can connect through. That would ensure that they always have access the secured server environment. However, strict security protocols would need to be in place on the bastion to ensure that it won't be compromised and used to access the secured environment.
I would recommend always using a passphrase with your ssh keys.
You can also limit access by requiring authentication through a VPN first. There are also cheap two-factor authentication methods like Yubikey.
I do agree that it takes a multi-layered approach, like anything with security. Good article!
There is no mention of APF + BDF or CSF? I would like to see a comment or testing of all those tools on CPU impact and comparison of functions and other details as well.
Good article.