How to Get a Static Outbound IP on Pantheon

Alex Dicianu , Senior Technical Support Engineer Reading estimate: 5 minutes

While working with clients and helping them create awesome websites, we’ve noticed a need for an outgoing static IP. Pantheon’s cloud architecture is awesome—based on Linux containers, it allows us to scale easily, migrate containers, restart them in just seconds without any customer impact, resize them, and so on. The downside of having such great flexibility is not being able to assign a fixed IP address to a container. This is sometimes needed by third-party applications that only accept connections from services they know about.

From a security perspective, I don’t exactly like this approach, as IP addresses can be spoofed, which tricks the external service into thinking it’s talking to a “trusted” server when it’s not. We usually recommend authentication-based schemes, using some form of data encryption or HMAC-based signatures for your data. These seem to work better and are more dynamic and flexible—you can change usernames, passwords, or the encryption keys at any time.

Implementing a more secure method can be quite expensive and in case of external services that require a fixed IP, it’s a bit impractical as they will have to implement it on their end as well. Elite customers benefit from Pantheon Enterprise Gateway, which acts as a gateway for outbound traffic and offers a fixed set of outbound IPs. Since we’ve seen this request from a few other customers as well, I’ve decided to implement a simpler version based on Nginx and Docker that would allow everyone to have an outbound fixed IP.

First of all, you need a server, ideally as close to the Pantheon data center as possible (our data center is hosted by Rackspace in Chicago, Illinois) that you can install and manage yourself. For demo purposes I chose DigitalOcean because it was easier for me to set up. Once the server is running, you can download this GitHub repository and run the installation process. If you don’t like using Docker, you can manually run the commands and configure the machine.

The way this works is pretty straightforward. Nginx sits outside your site on a machine with a static IP address. Whenever you want to make an outbound request (in this case I’ll use a simple curl command) instead of querying the third-party service, you’ll send the request to the nginx proxy that will forward it to its destination. The difference is that now the remote service will see the request coming from the proxy server—which has a fixed IP—and not from the internal network.

So, Let’s Get to It!

For demo purposes, I’ve launched two servers called nginxproxy and lamp. On the first I’m going to install the proxy. On the second I’m going to install a simple LAMP stack on which I’m going to simulate an API that’s simply going respond to every request by dumping the php $_SERVER variable (this is what we need to verify the client IP address).

First let’s see what IP address the nginxproxy server has.

$ ifconfig eth0 | grep inet

         inet addr:178.62.85.15  Bcast:178.62.127.255  Mask:255.255.192.0

         inet6 addr: fe80::601:84ff:fef9:4901/64 Scope:Link

 

It looks like the IP address is 178.62.85.15. Let’s keep this in mind for later and install the proxy.

$ git clone https://github.com/alexdicianu/nginx_proxy.git

Cloning into 'nginx_proxy'...

remote: Counting objects: 42, done.

remote: Compressing objects: 100% (32/32), done.

remote: Total 42 (delta 20), reused 21 (delta 6), pack-reused 0

Unpacking objects: 100% (42/42), done.

$ cd nginx_proxy/

 

Having copied the repository, it’s time to generate the self-signed SSL certificate. For testing this will do, but if you’re planning to run this for live sites, I strongly encourage you to run a fully qualified domain name and get a valid certificate for this part.

$ mkdir ssl

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl/server.key -out ssl/server.crt

Generating a 2048 bit RSA private key

.............................+++

writing new private key to 'ssl/server.key'

-----

Country Name (2 letter code) [AU]:US

State or Province Name (full name) [Some-State]:California

Locality Name (eg, city) []:San Francisco

Organization Name (eg, company) [Internet Widgits Pty Ltd]:Pantheon

Organizational Unit Name (eg, section) []:CSE

Common Name (e.g. server FQDN or YOUR name) []:example.com

Email Address []:

 

Next we’re going to generate a username and password for the HTTP authentication as an extra security measure.

$ htpasswd -c .htpasswd alex

New password:

Re-type new password:

Adding password for user alex

 

You might get a warning that htpasswd isn't installed. If you do, you can run sudo apt-get install apache2-utils to install it.

Once we have the username and password file, the last step before starting the proxy is to choose which host the proxy should forward the requests to. In this case, I’ve created a demo API under http://api.dicix.net/. Just edit the site.conf file and modify the proxy_pass value with, in my case, proxy_pass http://api.dicix.net/;

 

It’s now time to build and run the container.

$ docker build -t dicix/nginx_proxy .

$ docker run -d -t -p 80:80 -p 443:443 --name nginx_proxy dicix/nginx_proxy

 

If you’ve reached this point, it means you can start using your proxy. From my local computer (or from your site) you can start sending requests. I’m going to use a simple curl command, but you can also use a php script to build the request.

$ curl -vk http://alex:NginX%Proxy99@178.62.85.15/

HTTP/1.1 200 OK

Server: nginx/1.4.6 (Ubuntu)

Date: Tue, 10 Nov 2015 13:50:50 GMT

Content-Type: text/html

Content-Length: 1079

Connection: keep-alive

X-Powered-By: PHP/5.5.9-1ubuntu4.11

Vary: Accept-Encoding

 

Array

(

   [HTTP_HOST] => api.dicix.net

   [HTTP_CONNECTION] => close

   [HTTP_USER_AGENT] => curl/7.43.0

   [SERVER_SOFTWARE] => Apache/2.4.7 (Ubuntu)

   [SERVER_NAME] => api.dicix.net

   [SERVER_ADDR] => 178.62.5.172

   [SERVER_PORT] => 80

   [REMOTE_ADDR] => 178.62.85.15

   [PHP_AUTH_USER] => alex

   [PHP_AUTH_PW] => NginX%Proxy99

)

 

As you can see, the REMOTE_ADDR value is now the IP address of my nginxproxy server. You want this to be a static IP that you could then use for allowlisting.

Let's try HTTPS now. I'm only going to paste the relevant part referring to the SSL certificate.

$ curl -vk https://alex:NginX%Proxy99@178.62.85.15/

*   Trying 178.62.85.15...

* Connected to 178.62.85.15 (178.62.85.15) port 443 (#0)

* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384

* Server certificate: example.com

* Server auth using Basic with user 'alex'

Array

(

   [HTTP_HOST] => api.dicix.net

   [HTTP_CONNECTION] => close

   [HTTP_USER_AGENT] => curl/7.43.0

   [SERVER_NAME] => api.dicix.net

   [SERVER_ADDR] => 178.62.5.172

   [SERVER_PORT] => 80

   [REMOTE_ADDR] => 178.62.85.15

   ...

)

 

As you can see, even if the certificate is self signed, the handshake is established and you are able to use the proxy.

This is a simple proof of concept. In reality, you’ll likely use POST instead of GET and you’ll want to encrypt your data (hence the SSL part). Keep in mind this will be a server that communicates with the outside world, so please take all the necessary measures to secure it. At a very minimum, you should not run this server as root, use a firewall to close every port, except the ones you’re using for HTTP/HTTPS traffic and SSH (which you should probably change), use an antivirus and allow SSH access only via private/public keys. Please note that these are the very minimum security requirements and that you are responsible for securing this server.

Discover More

Halting DDoS Attacks: Effective Strategies for Prevention

Conor Bauer
Reading estimate: 9 minutes

Staying Ahead In The Game of Distributed Denial of Service Attacks

Steve Persch
Reading estimate: 3 minutes

Learn how healthcare sites can thrive with modern development tools

Jason Yarrington
Reading estimate: 3 minutes

Try Pantheon for Free

Join thousands of developers, marketers, and agencies creating magical digital experiences with Pantheon.