Transferring a Rails App to a new Server
– #Rails, #Operations, #How-To
Sometimes a web app has to be transferred to a new server. Here’s the process we use (for Rails apps, but should work for other kinds of webapps, too).
This guide makes the following assumptions:
- The app is served by nginx + Passenger.
- The database is running on the same server (no replication setup).
- No zero downtime requirement (but the downtime should be kept minimal).
Why do we need a downtime? Well, at some point we have to transfer all data (database contents, user generated files, …) from the old server to the new one. It is crucial that data is not being changed during or after this process.
After transferring the data, the app’s DNS records will be updated to point to the new server. However, since DNS changes take some time to propagate, and we want to keep the downtime as short as possible, we’ll configure the old server to act as a reverse proxy for the app on the new server at this stage. This way your users can instantly access the app on the new server using the familiar URL, without waiting for their cached DNS entries to update.
Let’s look into the details:
Preparation
Starting point: The new server is ready and accessible via new.yourapp.com
, but the app has not yet been deployed to it.
- Add HTTP-Auth to the new server (or limit the access your IP address) – you don’t want anyone else to access the app on the new server yet.
- Deploy the app to the new server. Check if it works correctly if accessed via
new.yourapp.com
. - Make sure that the app on the new server works correctly even if accessed via its original URL
www.yourapp.com
. You can do this eg. by temporarily modifying your hosts file. Don’t forget to undo these changes afterwards! - Optional: Lower the TTL of the DNS record for
www.yourapp.com
to something like 3600. This speeds up the propagation of the DNS update later. (But since we’re using a revere proxy setup, this is not strictly necessary.)
Migration Steps
- Bring the application on the old server into maintenance mode (see below for details).
- Stop all background jobs. If the application has any cronjobs, deactivate them, too.
- Create a database dump on the old server and import it on the new server. Also, transfer all uploads and other user generated content to the new server.
- Install cronjobs on the new server (if any).
- Check that the app works as expected on the new server (no missing data or files etc).
- Remove HTTP-Auth (or IP address based access restrictions) from the app on the new server.
- Activate the reverse proxy on the old server (details below).
- Update the DNS record of
www.yourapp.com
to point to new server (reset the TTL if you have lowered it previously).
That’s it! By activating the reverse proxy you made the app available again – only that it is now being served from the new server, even if users are still connecting to the old server because their cached DNS entries haven’t been updated yet.
Cleaning Up
You now have to wait long enough for the DNS change to propagate (this will depend on the DNS record’s TTL). When nobody is accessing the app via the reverse proxy on the old server anymore (check the nginx logfiles to be sure), you can take down the old server. Afterwards, you can also remove the DNS record for new.yourapp.com
(and the according entry in the nginx configuration on the new server).
Details, Details, Details …
Activating Maintenance Mode
Maintenance mode ensures that no activity takes place that causes data (in the database or filesystem) to be changed. Think of it as “read-only mode” for your app.
There are several ways to achieve this. One common way is to configure nginx to return a “503 Service Unavailable” response code for every request. This way, requests won’t even hit the Rails application.
Like with 404s, you can use a custom designed error page for these responses. Just be sure to have a file called 503.html
in your document root (in case of a Rails app, that’s usually the public/
folder):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
server_name www.yourapp.com;
location / {
return 503;
}
error_page 503 @offline;
location @offline {
rewrite ^(.*)$ /503.html break;
}
# … additional stuff snipped
}
To allow yourself to still access the application even in maintenance mode, simply wrap the return 503;
in a conditional:
1
2
3
4
# Replace 1.2.3.4 with the IP of the computer you’re currently working on
if ($remote_addr != "1.2.3.4") {
return 503;
}
Final notice: Remember that there may be other processes causing data to change, like background jobs or cronjobs. Make sure to disable them as well!
Setting up a Reverse Proxy
The reverse proxy allows your users to use the app on the new server even while they are technically still connecting to the old server. The old server just forwards all requests to the new server, fetches the responses and then presents the response to the user. The user can not even tell that the response has not been generated by the old server itself. (Of course the forwarding adds some milliseconds to the total response time, but this effect is usually negligible.)
To configure the old server as reverse proxy for the app on the new server, add the following lines to your nginx configuration (if you’ve already added a location block for maintenance mode, replace it now – there can only be one block for each location like /
):
1
2
3
4
5
6
7
8
9
10
11
12
13
server {
server_name www.yourapp.com;
location / {
proxy_pass https://new.yourapp.com;
proxy_set_header Host www.yourapp.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# … additional stuff snipped
}
Of all these directives, only proxy_pass
is strictly required. However, the other directives are helpful, too: For example they ensure that the Rails logs contain the IP addresses of your user, not the IP of the old server (which is where the requests to the new server are coming from, technically). See the docs for ActionDispatch::RemoteIp for details.