Your database server often contains your most sensitive data, such as confidential user information. It’s important to implement security best practices to minimize the risk of an attacker gaining access to your database.
This guide will be for MySQL in particular, as it’s the most common database, but most of these concepts will apply to any type of database—you’ll just require different configuration to achieve the same effects.
Make Sure to Run mysql_secure_installation
MySQL has a built-in tool that can handle a few basic security tasks. After you install MySQL, run the following command in your terminal:
This will walk you through a few tasks—setting a new password, removing anonymous users and test databases, and disabling remote root login. Afterwards, it will reload MySQL’s config for you, so you should see the changes immediately.
Lock Down SSH
This doesn’t technically have anything to do with the database itself; however, your database is only as secure as your server it is running on, so you’ll want to take the necessary steps to lock down SSH and prevent most of the common attack vectors.
You can read our full guide on SSH security to learn more, but the gist of it is a few steps:
- Disable password login, use SSH keys only.
- Rate limit failed login attempts with
denyhoststo prevent brute force.
- Whitelist access to trusted IP addresses.
- Disable root login over SSH—this way, attackers need to know your username.
- Set up Multi-Factor Authentication for SSH if you’re really paranoid.
Run a Standalone Database in a Private Subnet
Rather than running a local database on the same server that
nginx is running on, it’s better for security to run your database in a private subnet that is only accessible by other servers in your private cloud. Many cloud providers, including AWS, offer these features.
The setup looks like this:
Your Virtual Private Cloud (VPC) is simply the container that all of your AWS resources run in. Things in this cloud can talk to each other on their private IP addresses just like devices in your home would interact.
Servers in the public subnet will have public IP addresses, and will be directly accessible by anyone. Servers in the private subnet do not have public IP addresses—only a private one. They can still access the internet though, by using Network Address Translation, the same method that gives your computer access behind a home router.
This means that any servers launched in the private subnet will be entirely inaccessible from outside attackers. The only way your database could be accessed is if the web server is compromised, and even then it would still require a privilege escalation.
On top of the security benefits, this configuration is also much easier to scale. For example, you could have four web servers running WordPress that all connect to a single authoritative database server. This way, all four web servers are in sync, and you can easily scale up and down to meet demand, without worrying about the database. This can cause some performance issues due to the networking overhead, but with proper multilayer caching, the problem is minimal compared to the architectural benefits.
To make MySQL only accept connections from private connections, you’ll want to use a netmask in your GRANT statement. This doesn’t work with CIDR notation—only the full netmask.
GRANT ALL ON database.* TO 'user'@'10.0.1.0/255.255.255.0' IDENTIFIED BY 'password'
If you are going to only run one server, you should at least bind your database to localhost so that it isn’t accessible from the open internet anyway.
Bind to Localhost if Possible (or at Least a Different Port)
If you’re not accessing your database server from anything other than other processes running on the machine itself, you can lock it down so that it’s not accessible over the network. Of course, this only works if you have one server running everything, and that’s not the best configuration in the first place.
If you can’t bind to localhost, it’s a good idea to change the default port to something else.
Either way, you can accomplish this quite easily by binding MySQL to localhost, which means it will only listen on the loopback address, and not on any open ports. Open up
/etc/mysql/my.conf in your favorite text editor, and add the following line in the
bind-address = 127.0.0.1
You can also change the default port from this same section:
Restart MySQL, and you should see the changes.
Watch Your Shell History
Most things you do in Linux are logged. If an attacker manages to gain access to these log files, they could escalate their privileges by finding passwords stored in them.
This can happen in two main ways for MySQL, both of which are easily preventable. The first is when you log into the MySQL shell—you never want to specify the password as an argument on the command line. Always leave it blank, and let MySQL ask for it.
mysql -u root -p
Otherwise, it’s visible using the
MySQL also has its own history file, stored at
~/.mysql_history. This obviously doesn’t log the password you use to login, but if you created a new user from the MySQL command line, that statement will be logged—password and all. You’ll want to clear this file as a precaution if you ever have to modify user accounts.
# cat /dev/null > ~/.mysql_history
If You’re on AWS, Consider Using RDS
AWS’s Relational Database Service (RDS)is a fully managed database in the cloud. If you don’t want to have to worry about database security, and just want a deployable solution, RDS (and all of AWS’s other managed database services) will take away the headache.
RDS uses AWS’s own Identity and Access Management (IAM) system. IAM manages permissions for everything you do on AWS. Every action in the web-based Management Console, CLI commands, and API requests must all go through IAM. Any action will be blocked if the user or entity executing doesn’t have explicit permission to access the given resource. On top of that, RDS is private by default, so you won’t have to worry about attacks from the open internet.
You’ll still end up using standard MySQL username and password authentication to connect to the database itself though—IAM simply secures everything else about the database, such as the configuration. However, you can use AWS’s Secrets Manager, which allows you to constantly rotate security keys without code redeployments, and has built in integration for RDS to boot.
If you want, you can also turn on IAM Database Authentication, which will allow you to bypass password auth altogether and just use IAM. However, it adds some overhead, and as such, is rate-limited to 200 new connections per second for MySQL.