How to Code a Proxy in Python: A Safe HTTP Tutorial
TL;DR
Coding a proxy is a useful way to learn HTTP routing, but a tutorial proxy should remain bound to localhost and restricted by a destination allowlist.
The example below implements a small HTTP forward proxy in Python, supports GET, rejects HTTPS CONNECT, removes hop-by-hop headers, and applies an upstream timeout.
A production proxy also needs authentication, access control, audit logs, resource limits, abuse handling, TLS policy, monitoring, and careful DNS and URL validation.
Test the proxy against a local server before allowing any public destination, and never expose the listening port directly to the internet.
For authorized automation that needs managed routing rather than a learning project, authenticated Nstproxy Residential Prime Proxies support documented HTTP, HTTPS, and SOCKS5 access.
What Does It Mean to Code a Proxy?
Coding a proxy means building an intermediary that receives a client request, validates it, opens a connection to an approved destination, relays the request, and returns the response. This guide focuses on an HTTP forward proxy, not JavaScript's Proxy object, a reverse proxy, a VPN, or an anonymous public relay.
For authorized application routing beyond this learning exercise, Nstproxy Residential Prime Proxies provide a managed product path; the tutorial itself remains local and provider-neutral.
A forward proxy acts for the client; a reverse proxy acts in front of one or more origin servers. The distinction matters because the security boundary, request format, and deployment controls are different. The MDN proxy and tunneling guide explains both roles and describes how the HTTP CONNECT method creates a tunnel for TLS traffic.
The learning implementation in this article has an intentionally narrow contract:
Capability
Included
Reason
Absolute-form HTTP requests
Yes
A forward proxy receives a full target URL from the client.
GET requests
Yes
Enough to demonstrate parsing, validation, forwarding, and relaying.
Destination allowlist
Yes
Prevents the tutorial server from becoming an unrestricted relay.
Upstream timeout
Yes
Bounds stalled connections.
HTTPS CONNECT
No
Tunneling needs separate authorization, port policy, and bidirectional relay logic.
Caching, authentication, and observability
No
These belong in a production design, not a compact teaching example.
For a refresher on request lines, headers, and status codes, review Nstproxy's HTTP glossary. If your actual goal is to front an application server, the reverse proxy definition is the more relevant starting point.
Move From a Learning Proxy to Managed Proxy Infrastructure
Use authenticated proxy endpoints when you need dependable routing, rotation, and protocol support without maintaining an open proxy.
The safest way to learn how to code a proxy is to build a bounded HTTP-only version, run both ends on localhost, and verify each behavior before widening access.
Method 1: Build a Bounded HTTP Forward Proxy in Python
This method uses Python's standard library so the important network behavior stays visible. The Python http.server documentation also warns that the module is not designed for production, which is exactly why this implementation is presented as a controlled learning project.
Step 1: Save the Proxy Server
Create proxy_server.py with the following code:
from __future__ import annotations
import argparse
import socket
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlsplit
ALLOWED_HOSTS ={"127.0.0.1","localhost","example.com"}HOP_BY_HOP ={"connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade",}classForwardProxy(BaseHTTPRequestHandler): protocol_version ="HTTP/1.1"defdo_GET(self)->None: target = urlsplit(self.path)if target.scheme !="http"ornot target.hostname: self.send_error(400,"Use an absolute http:// URL")returnif target.hostname notin ALLOWED_HOSTS: self.send_error(403,"Host is not on the allowlist")return port = target.port or80 path = target.path or"/"if target.query: path +="?"+ target.query
headers =[f"GET {path} HTTP/1.0",f"Host: {target.hostname}:{port}","Connection: close",]for key, value in self.headers.items():if key.lower()notin HOP_BY_HOP and key.lower()!="host": headers.append(f"{key}: {value}")try:with socket.create_connection((target.hostname, port), timeout=5)as upstream: upstream.sendall(("\r\n".join(headers)+"\r\n\r\n").encode("latin-1"))while chunk := upstream.recv(64*1024): self.connection.sendall(chunk)except(OSError, TimeoutError)as exc: self.send_error(502,f"Upstream failed: {exc}")defdo_CONNECT(self)->None: self.send_error(501,"CONNECT tunneling is intentionally not implemented")defmain()->None: parser = argparse.ArgumentParser(description="Bounded educational HTTP proxy") parser.add_argument("--host", default="127.0.0.1") parser.add_argument("--port",type=int, default=8080) args = parser.parse_args() server = ThreadingHTTPServer((args.host, args.port), ForwardProxy)print(f"Listening on http://{args.host}:{args.port}", flush=True) server.serve_forever()if __name__ =="__main__": main()
The allowlist is the central safety control in this example. A real service must also resolve the hostname, reject private, loopback, link-local, and metadata-service addresses when they are not explicitly intended, then repeat that validation across redirects. The OWASP SSRF prevention guidance provides a fuller validation model.
Step 2: Start a Local Target and the Proxy
Open two terminals in the same directory. Start a local target in the first:
Binding both processes to 127.0.0.1 keeps the test reachable only from the same machine. Do not change the proxy host to 0.0.0.0 unless a firewall, authentication layer, and explicit client network policy are already in place.
Step 3: Send a Request Through the Proxy
Use a third terminal to force curl through the local proxy:
A successful response begins with the contents of proxy_server.py. In the verification run for this article, the proxy printed Listening on http://127.0.0.1:8080, and the client received the file through the proxy. This checks the full path: client β proxy β local origin β proxy β client.
Step 4: Test the Failure Boundaries
A useful proxy test verifies rejection paths, not only the happy path. Try an HTTPS URL and confirm the server returns 501, because the tutorial intentionally does not implement CONNECT. Try a hostname outside ALLOWED_HOSTS and confirm the response is 403.
The HTTP Semantics specification for CONNECT notes that arbitrary tunnel destinations carry significant risk and recommends restricting them to a limited set of known ports or safe targets. Treat that as a production requirement, not an optional enhancement.
Method 2: Use a Managed Proxy Endpoint in Application Code
Use a managed endpoint when the business requirement is routing, location control, session behavior, or traffic operations rather than learning protocol mechanics. Maintaining a custom proxy means owning authentication, patching, IP reputation, capacity, abuse response, logging, and availability.
Nstproxy Residential Prime Proxies are a fit for authorized data collection, price monitoring, ad verification, and network testing when a team needs managed proxy access instead of a hand-built relay. Current first-party documentation lists HTTP, HTTPS, and SOCKS5 protocol support plus Package and pay-per-use billing models; this article intentionally avoids publishing numeric prices. The product surface also describes selectable session behavior and geographic targeting, which should be confirmed for the exact plan before implementation. Nstproxy does not replace target-site permission, rate policy, or application-level validation. Choose it when managed routing and operational controls solve the actual problem.
Documented proxy protocols: Choose HTTP, HTTPS, or SOCKS5 according to the client library and destination workflow rather than modifying the learning proxy to cover every protocol.
Flexible billing model: The current product surface offers package and usage-based options, so teams can compare billing against accepted records or completed checks without relying on a homemade open relay.
Session and targeting controls: Use the current dashboard and product documentation to select location and session behavior, then validate the output against the business requirement.
Production Requirements the Tutorial Does Not Cover
A production proxy needs controls at every boundary, because forwarding a valid HTTP request is only the smallest part of the system.
Authentication and authorization: Identify each client, scope destinations and methods, rotate credentials, and revoke access promptly.
Destination validation: Parse once, normalize carefully, resolve DNS, block unsafe address ranges, limit redirects, and defend against DNS rebinding.
Protocol correctness: Support message framing, body streaming, keep-alive, trailers, upgrades, CONNECT, and cancellation without request smuggling or desynchronization.
Resource limits: Bound header size, body size, concurrency, connection lifetime, bandwidth, and queue depth.
Observability: Record request IDs, timing, destination class, outcome, and bytes without logging secrets or full sensitive URLs.
Abuse handling: Enforce acceptable-use policy, rate limits, anomaly detection, and an incident-response path.
Deployment security: Run with least privilege, patch dependencies, isolate the service, and expose only the intended listener.
The tutorial strips common hop-by-hop headers, but it is not a complete implementation of modern HTTP framing. It also forwards only GET, converts the upstream request to HTTP/1.0, and closes the upstream connection after each response. Those choices make behavior easier to inspect; they do not make the code production-ready.
Common Problems and Fixes
Most first attempts fail because the client is not sending proxy-form requests, the destination is not allowed, or the request requires HTTPS tunneling.
The Client Connects Directly
Set the client proxy explicitly and disable any no_proxy rule for the test. The example curl command uses --noproxy '' so localhost traffic does not bypass the proxy.
The Proxy Returns 400
The proxy returns 400 when the request target is not an absolute http:// URL. Forward proxies receive an absolute-form URI, while origin servers usually receive only the path and query.
The Proxy Returns 403
The destination hostname is not in ALLOWED_HOSTS. Add only a domain you own or are authorized to test, and retain IP-range validation before any wider deployment.
HTTPS Returns 501
The example rejects CONNECT by design. Adding a bidirectional TCP tunnel without authentication and destination restrictions can create an open proxy, so use a mature server or managed service for real HTTPS workflows.
Conclusion: Build for Learning, Buy for Operations
The best answer to how to code a proxy is to start with a deliberately small HTTP forwarder and prove its routing and rejection behavior on localhost. Keep the allowlist, timeouts, method restrictions, and local binding intact while learning; choose a mature proxy server or managed provider when the requirement expands beyond inspection and education. Your next action should be to run the local test, confirm the 403 and 501 paths, and write down the production controls your use case would require. For workflows that later need centralized pools, routing rules, logs, and monitoring, evaluate Nstproxy Proxy Manager as a separate operational layer.
Experience Nstproxy β Start Your Free Trial Today
Q: What programming language is best for coding a proxy?
Python is a practical teaching language because its standard library exposes sockets and HTTP handlers clearly. Go, Rust, Java, and C can be better choices for specific performance or deployment constraints, but language choice does not remove the need for protocol correctness and security controls.
Q: Can this Python proxy handle HTTPS?
No, the tutorial proxy intentionally rejects HTTPS CONNECT requests with 501. Production HTTPS tunneling needs authentication, a destination and port policy, full-duplex relaying, timeouts, cancellation, and careful logging.
Q: Is it legal to run a proxy server?
Running a proxy is generally a technical capability, but lawful use depends on jurisdiction, authorization, data, contracts, and the destination's rules. Use the example only on systems you own or have permission to test, and obtain legal advice for high-risk or regulated workflows.
Q: Why should the proxy bind to localhost?
Binding to localhost prevents other machines from reaching the unfinished tutorial service. An internet-reachable proxy without authentication and access controls can be abused as an open relay and can shift operational and legal risk to its operator.
Q: How do I know requests really passed through the proxy?
Check both ends: the proxy should log the client request, and the local target should log the proxy's upstream request. The returned body should match the target resource, while a direct client request made without the proxy should produce no new proxy log entry.
Q: Should I build or buy proxy infrastructure?
Build a small proxy to learn or satisfy a tightly controlled internal requirement; use mature or managed infrastructure when you need authentication, reliability, protocol coverage, session controls, geographic routing, monitoring, and abuse response. Compare the total operational burden, not only the amount of code.
Lena Zhou
Aug. 17th 2026
110M+ real IPs with 99.9% access success
Blazing-fast average response ~0.5s for high-concurrency tasks
From only $0.1/GB
Get immediate access to premium residential, datacenter, IPv6 and ISP proxy pools.