Infrastructure · Networking

Know which door
you are opening.

Trace a request to your Ubuntu server, move the demo page inside an SSH tunnel, and close its direct internet HTTP port.

These are sample names, not our infrastructure. Before copying commands, read Customize the examples: paths, keys, IPs and names. Substitute your own authorized account and resources. Existing lab? Retain its original resource names, usernames and state; renamed samples can cause replacement. See the existing-lab warning before applying updated recipes.

Reuse one disposable lab

Complete one of the server guides above, including Ansible configuration and host-fingerprint verification. Use only its code-managed sandbox. You do not need three running servers to complete this lesson.

A route is a road sign. A firewall is the doorman. A listening application is someone inside who can answer. A working website needs all three—not just an open port.

These changes concern only the lab. Keep the Terraform folder/state and a verified SSH login available. We will close port 80, not port 22. No NAT gateway, load balancer, certificate or new cloud resource is required. Existing VM, disk, IP and traffic costs continue.

Use Ubuntu/WSL on Windows, as in the server lessons. All terminal commands below run in that environment, except commands explicitly marked “inside the server.”

1. Trace the request through five layers

Your laptopCurrent public source IP
Cloud networkAddress + route + firewall
Ubuntu applicationListening port + response
  1. Address: use the server's current public IP, not its private subnet IP. An IP can change after stop/start.
  2. Route: AWS's subnet routes to an internet gateway. Azure uses system routes and the explicit NIC public IP. Google uses the custom VPC's internet route and external IP.
  3. Firewall: verify the source /32, TCP port and target. Azure may have subnet and NIC NSGs; Google rules apply to tagged targets and can be affected by higher-level policies.
  4. Guest: an OS firewall or application binding can still block the request. A cloud allow does not override them.
  5. Application: Nginx must be running and listening on port 80. SSH on 22 is a different service.
Inside Ubuntu · inspect listeners and local response
sudo ss -lntp
systemctl status nginx --no-pager
curl --max-time 5 -I http://127.0.0.1/

A local HTTP 200 response proves the application works locally. It does not prove the external firewall is correct. A timeout can mean several different failures; collect evidence before changing access.

Address/rangeMeaningNot the same as
127.0.0.1This computer's loopback interfaceYour remote server unless the command runs there
Your public IPv4 /32One source addressOne human—other users may share the same NAT/VPN address
0.0.0.0/0 routeDefault route for IPv4 destinationsAuthorization to log in
0.0.0.0/0 inbound sourceAny IPv4 sourceA safe SSH troubleshooting shortcut

In every new laptop terminal, set LAB_SSH_KEY to your actual private-key file, not its .pub partner. For example, LAB_SSH_KEY="$HOME/.ssh/contoso_aws" works only if you created that AWS key there. Choose your Azure or Google key for those servers. See key paths on Windows, WSL and Linux; never copy a Windows path into WSL unchanged.

2. Put HTTP inside an encrypted SSH tunnel

Instead of opening the front web door, send the page down the locked SSH hallway you already verified.

In a new Ubuntu/WSL terminal, choose your cloud's command, replace the server IP, and unlock your passphrase if asked. The tunnel binds only your laptop's loopback address—not every device on your home network.

Ubuntu / WSL · AWS tunnel
ssh -N -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:8080:127.0.0.1:80 \
  -i "${LAB_SSH_KEY:?Set LAB_SSH_KEY to your private-key path}" ubuntu@YOUR-SERVER-IP
Ubuntu / WSL · Azure tunnel
ssh -N -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:8080:127.0.0.1:80 \
  -i "${LAB_SSH_KEY:?Set LAB_SSH_KEY to your private-key path}" contoso@YOUR-SERVER-IP
Ubuntu / WSL · Google Cloud tunnel
ssh -N -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:8080:127.0.0.1:80 \
  -i "${LAB_SSH_KEY:?Set LAB_SSH_KEY to your private-key path}" contoso@YOUR-SERVER-IP

The command stays open without a shell prompt; that is expected. The first loopback address is on your laptop. The second is interpreted from the cloud server. Open http://127.0.0.1:8080/ or use another terminal:

Second Ubuntu / WSL terminal · check the tunnel
curl --max-time 5 -I http://127.0.0.1:8080/

On Windows, localhost forwarding usually lets the Windows browser reach the WSL tunnel. If your WSL networking differs, use curl inside the same Ubuntu environment; do not rebind the tunnel to 0.0.0.0. If port 8080 is occupied, consistently choose another unused local port, such as 8081.

This is not HTTPS certificate setup. The internet portion is encrypted by SSH, but your browser sees HTTP to localhost. It is a temporary administration/demo path, not a public website or a replacement for production TLS. Do not disable host-key checking.
Prove it: the page is reachable through the tunnel before you change any firewall rule.

3. Remove only the direct HTTP allowance

Work in the original Terraform folder for the chosen cloud. Keep the current /32 SSH rule intact. Make the change in code so a later apply does not accidentally restore it.

  • AWS: delete only the entire aws_vpc_security_group_ingress_rule "http" resource block. Keep the SSH resource, outbound rule, routes and server.
  • Azure: remove only the security_rule block named LearnerHTTP from the NSG. Keep LearnerSSH and DenyOtherInbound.
  • Google: in the learner firewall resource change its allow block to the one below. Keep its source /32 and target tag.
Google Terraform · replacement allow block only
allow {
  protocol = "tcp"
  ports    = ["22"]
}
Ubuntu / WSL · review the networking change
terraform fmt
terraform validate
terraform plan -out=network.tfplan
# Expect only removal of port 80 access, not VM replacement or new services.
terraform apply network.tfplan

If the plan proposes replacing the VM because a newer image was discovered, stop. Review/pin the approved image and regenerate the plan instead of combining changes. Also stop for unexpected billing, account, firewall or deletion changes. Saved plans apply without another confirmation.

4. Compare a blocked path with a working path

Ubuntu / WSL · test new connections, not a cached page
# Direct HTTP should no longer succeed in this isolated lab.
curl --max-time 5 -I http://YOUR-SERVER-IP/
# The SSH tunnel should still return HTTP 200.
curl --max-time 5 -I http://127.0.0.1:8080/

Test a fresh connection and inspect the actual rules. Stateful cloud firewalls may let previously tracked connections continue after a rule change, and browsers can cache content. A single timeout alone is not proof of correct policy.

If direct access still works, check other attached security groups, NIC/subnet rules, target tags and inherited policies. Do not disable a shared policy to make the result fit this lab. Ask the administrator to help interpret effective rules.

Prove it: a new direct connection is blocked, your source-restricted SSH path works, and the tunneled page returns 200. Terraform agrees with the firewall configuration.

AWS tracked connectionsAzure effective traffic rulesGoogle firewall evaluation

5. A tunnel is not a private subnet

The VM still has a public IP and source-restricted SSH. Removing that IP without another access and outbound path can lock you out and break package downloads.

CloudPrivate administration optionRequirements to plan first
AWSSystems Manager Session ManagerAgent, authorized instance role, user session permissions and service connectivity via approved egress or endpoints.
AzureAzure Bastion or an approved private connectionSupported topology/SKU, permissions, subnet/rules and cost. Do not assume a Bastion deployment is free.
Google CloudIAP TCP forwarding with approved OS accessTunnel IAM, SSH/OS Login permissions and the required IAP source rule. It is a different source path from your laptop /32.

Private servers can still need paid NAT, endpoints or another egress design for updates. NAT is outbound connectivity, not an administrator login service. A private address does not automatically grant identity or protect an insecure application.

Session Manager prerequisitesAzure BastionBastion pricingGoogle IAP setup

These are architecture planning options, not an implemented migration in this lesson. Keep your working lab access until a replacement path is deliberately built and tested.

6. Close the tunnel and finish the lab

Press Ctrl+C in the tunnel terminal. Confirm localhost:8080 no longer serves the cloud page. Closing a tunnel does not stop or delete the VM.

Keep the port-80 removal in your recipe, or deliberately restore that lab-only rule through a reviewed plan if repeating the original demo. Use the chosen server guide's full cleanup when finished: