In this article, we’ll go through the process of reverse tunneling to expose local running server to outside world with EC2 instance. That means you’ll be accessing the local server as a web server without static IP.
For example, if you’re running a local server on your machine then you can access this with http://<INSTANCE_IP>:9876
by using an EC2 Instance that is configured as a reverse tunneling server. You can even access using the Elastic IP of your instance or domain address (if you set it up).
Prerequisites
- AWS account and credentials
- Application server running locally (In this I’ll be using a simple Python Flask server)
Expose Local Running Server to Outside World With EC2 Instance
Step-1: Create and Configure EC2
First, log in to your AWS account console and search for EC2 from the search bar. And follow the following steps:
Create EC2 Instance
- From EC2 dashboard, select Instances and click on Launch Instances from the corner.
- Name the server and select instance type as
t2.micro
(It’s free tier eligible). Click on Create new key pair. Name the key pair and click create key pair. Save the keypair file safely for future reference.
- Now, create a new security group. Leave all configurations defaults for now, we’ll modify them later. And click on Launch Instance.
- Wait for the Instance to be in the state of running.
Configure Security Groups
You have created an instance and next we’re going to open port 9876
(you can use any port number) to the public. For this do the following:
- Go to Instance page and click on instance id.
- In the description window, select security tab and click on the security group id from Security groups section.
- On the next page click on Edit inbound rules.
- Configure the following by clicking Add rule:
- Type: Custom TCP
- Port range: 9876
- Source: Anywhere
You have successfully created and configured an EC2 instance for reverse tunneling.
Step-2: Edit Server’s SSH config file
SSH into the server
Now open your local terminal and run the following command to ssh into the server:
$ ssh -i "demo-server-key.pem" ec2-user@3.26.208.34
The authenticity of host '3.26.208.34' can't be established.
ED25519 key fingerprint is SHA256:5RZDJv0FznShXjPK4wBcf1zk0P6O7zGp995RnlDEeQE.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '3.26.208.34' (ED25519) to the list of known hosts.
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\\___|___|
<https://aws.amazon.com/amazon-linux-2/>
3 package(s) needed for security, out of 7 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-172-31-37-246 ~]$
Configure reverse tunneling
Now, edit /etc/ssh/sshd_config
file and change the # GatewaPyports no
to GatewayPorts yes
by removing #
sign. OR you can add a new line GatewayPorts yes
.
...
# WARNING: 'UsePAM no' is not supported in Red Hat Enterprise Linux and may cause several
# problems.
UsePAM yes
#AllowAgentForwarding yes
#AllowTcpForwarding yes
GatewayPorts yes
X11Forwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
...
- Then restart the SSH service:
$ sudo systemctl restart sshd.service
- Check SSH daemon status:
[ec2-user@ip-172-31-37-246 ~]$ sudo systemctl status sshd.service
● sshd.service - OpenSSH server daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2022-08-26 09:45:59 UTC; 8s ago
Docs: man:sshd(8)
man:sshd_config(5)
Main PID: 3466 (sshd)
CGroup: /system.slice/sshd.service
└─3466 /usr/sbin/sshd -D
Aug 26 09:46:04 ip-172-31-37-246.ap-southeast-2.compute.internal sshd[3471]: Connection closed by 43.138.206.182 port 54120 [preauth]
Aug 26 09:46:06 ip-172-31-37-246.ap-southeast-2.compute.internal sshd[3477]: Invalid user es from 43.138.206.182 port 54144
Aug 26 09:46:06 ip-172-31-37-246.ap-southeast-2.compute.internal sshd[3477]: input_userauth_request: invalid user es [preauth]
Aug 26 09:46:06 ip-172-31-37-246.ap-southeast-2.compute.internal sshd[3479]: Invalid user esuser from 43.138.206.182 port 54154
Aug 26 09:46:06 ip-172-31-37-246.ap-southeast-2.compute.internal sshd[3479]: input_userauth_request: invalid user esuser [preauth]
Aug 26 09:46:06 ip-172-31-37-246.ap-southeast-2.compute.internal sshd[3478]: Invalid user ansible from 43.138.206.182 port 54146
Aug 26 09:46:06 ip-172-31-37-246.ap-southeast-2.compute.internal sshd[3478]: input_userauth_request: invalid user ansible [preauth]
Aug 26 09:46:06 ip-172-31-37-246.ap-southeast-2.compute.internal sshd[3479]: Connection closed by 43.138.206.182 port 54154 [preauth]
Aug 26 09:46:06 ip-172-31-37-246.ap-southeast-2.compute.internal sshd[3477]: Connection closed by 43.138.206.182 port 54144 [preauth]
Aug 26 09:46:06 ip-172-31-37-246.ap-southeast-2.compute.internal sshd[3478]: Connection closed by 43.138.206.182 port 54146 [preauth]
If it’s all working!! You’re good to go through reverse tunneling.
Step-3: Run Local Server and Initialize Reverse Tunneling
Now, it’s time to run your local server. I have a Flask application server on port 5000 by installing Python and Flask packages.
My Flask application files tree:
.
├── app.py
├── requirements.txt
└── templates
├── about.html
├── base.html
└── home.html
1 directory, 5 files
app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
return render_template("home.html")
@app.route("/about")
def about():
return render_template("about.html")
if __name__ == "__main__":
app.run(debug=True)
about.html:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>About Website</title>
</head>
<body>
{% extends "base.html" %}
{% block content %}
<h1> About Blog </h1>
<p> This is just an example for tunneling.</p>
{% endblock %}
</body>
</html>
base.html:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Tunneling Example</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}">
</head>
<body>
<header>
<div class="container">
<h1 class="logo">Tunneling from ec2 to local</h1>
<strong><nav>
<ul class="menu">
<li><a href="{{ url_for('home') }}">Home</a></li>
<li><a href="{{ url_for('about') }}">About</a></li>
</ul>
</nav></strong>
</div>
</header>
{% block content %}
{% endblock %}
</body>
</html
home.html:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Flask Application for Tunneling demo</title>
</head>
<body>
{% extends "base.html" %}
{% block content %}
<h1> Tunneling demo project</h1>
<p> This is a Reverse Tunneling demo i.e. from cloud to local. </p>
{% endblock %}
</body>
</html>
Now run the Flask server on your local machine:
$ python app.py
* Serving Flask app 'app' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on <http://127.0.0.1:5000> (Press CTRL+C to quit)
* Restarting with watchdog (inotify)
* Debugger is active!
* Debugger PIN: 307-983-135
127.0.0.1 - - [26/Aug/2022 15:51:25] "GET / HTTP/1.1" 200 -
Start reverse tunneling. To do so run the following command from your local terminal:
$ ssh -i demo-server-key.pem -R 9876:127.0.0.1:5000 ec2-user@3.26.208.34
Here, port 9876 of demo-server is forwarded to 5000 as my Flask app is running in port 5000.
Step-4: Test the server
With the local machine’s localhost:
Now test with EC2 instance’s IP with port 9876
i.e. http://<IP>:9876
:(If you have a domain set up you can use that also)
Or with Instance’s IPV4 Public DNS:
Congratulations!! you have successfully implemented reverse tunneling with EC2 to make your locally running server accessible from the outside world.
That’s it.
Conclusion
In this article, you went through how to Expose local running server to outside world with EC2 instance as reverse tunneling.
Also, check out my new article on how to connect a private rds using ec2 as a bastion host.
Thank you!