If you use Google App Engine you probably already know that you can use a custom domain mapped to your application server on the appspot.com domain. Unfortunately, the custom domain works only when accessed access via http. If you want to use SSL with the custom domain, you’re out of luck.
Until GAE adds support for custom SSL certificates, your best bet is to use a reverse proxy in front of your appspot.com application. For me, as a developer, messing up with SSL certificates and web server/proxy configuration sounded somewhat complicated and time consuming but it all turned out to be quite simple.
I assume in this post that you’re moderately familiar with Linux shell script and have a server access to install and configure your reverse proxy. If you already use a custom domain for your GAE application, you’ll have to change your DNS settings to point to your reverse proxy server’s IP address instead to ghs.google.com. (You don’t have to remove domain from Goolge yet.)
Why Nginx?
Well… I started using Apache at first. It comes as a part of the default Linux distribution and we already run our web sites on it. There’s enough information about settings up Apache as a reverse proxy and that was not an issue. However, once our Apache-based reverse proxy was up and running it was slow… depressingly slow. The first HTTP request (that opens connection to web server and includes SSL handshake) was taking as much as 5 seconds. The subsequent requests (within keep-alive time) were fast, as long as the connection was alive. I was somewhat discouraged by various claims that SSL adds a significant CPU overhead but fortunately this was not an issue…
Further research took me to consider Pound and Nginx as possible alternatives to Apache. Reading about experiences with these two and especially after reading this post about Nginx’s performance with SSL, I decided to go with Nginx. It turned out quite well!
Let’s start…
Get and install Nginx
Login to your future reverse proxy system and:
sudo apt-get install nginx
Get SSL certificate
Prices for SSL certificates vary. We took GoDaddy‘s Standard SSL (it’s $60 for 1 year although you can easily find a coupon and get it somewhat cheaper). After your certificate request has been verified, go and download certificate files. GoDaddy has no download for Nginx so choose Apache. You will get a ZIP containing two files:
- <Common Name>.crt – Your certificate
- gd_bundle.crt – GoDaddy Certificate Intermediates Bundle
(For more detailed explanation on obtaining certificate you may find this useful.)
Concatenate certificates
Nginx’s ssl_certificate directive accepts a single SSL certificate file (no alternative for Apache’s SSLCertificateChainFile) so you need to concatenate your site’s and GoDaddy’s chain crt file:
cat mysite.crt gd_bundle.crt > mysite_and_gd_bundle.crt
Remove passphrase from your key file
You should remove passphrase from your key file if you want Nginx to be able to startup without asking you for the key passphrase.
cp mysite.key mysite.key.bak openssl rsa -in mysite.key.bak -out mysite.key
Reverse proxy configuration
It’s time to configure Nginx:
cd /etc/nginx/sites-available
create a new file named secure.mysite.com (using your site name obviously), copy and customize configuration:
server {
listen 443;
server_name secure.mysite.com;
access_log /<path-to>/access.log;
error_log /<path-to>/error.log;
keepalive_timeout 70;
ssl on;
ssl_certificate /<path-to>/mysite_and_gd_bundle.crt;
ssl_certificate_key /<path-to>/mysite.key;
ssl_session_timeout 30m;
location / {
proxy_pass https://<my-gae-app-id>.appspot.com;
proxy_set_header Host <my-gae-app-id>.appspot.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_intercept_errors on;
error_page 500 = /error_page.html;
}
location = /error_page.html {
root /local_path_to_static_files_root;
}
}
Explanation:
- Listen port 443 only (for https). You may want to specify IP address too if your system has more than one IP address.
- Server name secure.mysite.com is an external server name for which we’ve obtained the certificate.
- We proxy_pass to our app on GAE using https. (You could use http too but I don’t recommend it as an overhead is really low. We didn’t see significant latency or increased CPU usage raising from using https here.)
- Set Host header to our app’s server name at GAE. Without it GAE will not know which application you’re looking for.
- Our application on GAE will see all requests coming from our proxy server’s IP address so we send additional header with user’s IP address.
- We intercept our app’s errors (line 22) and in case of error 500 (line 23) we show user a static page located on our proxy server (lines 26-28). This is finally a way to serve static page on GAE’s error 500.
Completing Nginx setup
Nginx will handle only SSL connections so disable its default site configuration (listening on port 80) and activate our site’s config:
cd /etc/nginx/sites-enabled sudo rm default sudo ln -s ../sites-available/secure.mysite.com .
Nginx is ready. One more thing…
Update Apache config
Apache is still listening on port 80 which means it will receive requests to http://secure.mysite.com. This is not what we want so update its config to redirect all requests to https:
<VirtualHost *:80>
ServerName secure.mysite.com
RewriteEngine On
DocumentRoot /local_path_to_static_files_root
<Directory "/local_path_to_static_files_root/">
AllowOverride All
Order allow,deny
allow from all
RewriteRule ^(.*) https:/secure.mysite.com/$1 [R,L]
</Directory>
Redirect permanent / https://secure.mysite.com/
</VirtualHost>
(I’m sure it’s not perfect but it worked for me.)
To make sure Apache is not listening on 443, disable its SSL module:
sudo a2dismod ssl sudo /etc/init.d/apache force-reload
That’s it!
We’re ready to start Nginx:
sudo /etc/init.d/nginx start
and try to open https://secure.mysite.com
That’s it! We got a branded, secure URL for our application for about $50 (annually for SSL certificate) and few hours of administration time. We used our “regular” web server (it was idling anyway) to host Nginx and so far it’s coping with the load very well. (If we see it struggling at any time, we’ll be more than happy to upgrade it. As we don’t offer real Freemium, almost all of our users are customers so we’d probably be happy to have an issue with the proxy load.)
Additional considerations
- Many reverse proxy configuration samples that I’ve found use “proxy_redirect off” option. My application didn’t work well with it so I removed it (“on” is default). If you have issues with redirects you may want to check this option.
- Nginx supports additional options for SSL negotiation (ssl_protocols, ssl_ciphers, etc.) that may be used to fine tune SSL handshake performance. I still don’t fully understand all of these so I left it alone.
- You may want to set Nginx’s worker_processes to the number of processors your server has.
Using Java? Speed-up bug fixing and improve customer satisfaction using LogDigger to create detailed error reports and notifications for your web application
{ 5 comments… read them below or add one }
Thanks a ton. This really helped me when I was setting up the same type of solution. One thing I have noticed though is that “proxy_set_header X-Real-IP $remote_addr;” does not pass the IP in a way that convinces AppEngine to put it in its logs instead of the IP of my reverse proxy server. Do you know of a way to send the client IP so that AppEngine uses it in its logs? I know in my code I can grab the client IP from the “X-Real-IP” header of the request object but I can’t figure out how to have it show in the logs.
Thanks very much for the guide.
- Bryce
Glad to hear you’ve found this useful. I don’t think it’s possible to save the real IP header in Google’s logs (unless they support this custom header). Possibility to generally replace proxy’s IP with a value from HTTP header probably should not be expected as anyone could provide a fake header to spoof logs.
For applications that we run on GAE we use LogDigger to collect detailed error reports in which case we get all HTTP headers including real IP.
(This is a bit a shameless plug as I authored LogDigger. The web site is still in flux as we’re releasing these days but you can find details about how to use it with GAE apps here.)
Just a thought, but failing a better solution I can use the logging module to put the X-Real-IP value into the logs as info during the init of my request handler.
I have been playing around with your other project BugDigger. I like it and will probably get my testers to try it.
I thought of the header spoofing and I figure as long as the IP GAE claims the request came from is the same as the IP of my reverse proxy then trusting the contents of the X-Real-IP header is no worse than trusting the IP GAE claims the request came from which is all I really had to start with.
Using the logging module puts the X-Real-IP in the GAE logs as expected. Sweet.
Yes, logs rule. Too bad GAE lets you download only access logs, not application logs. (Well… at least I couldn’t find an option to do that in GAE SDK for Java.)