Proksi: Automatic SSL, HTTP, and DNS Proxy
Proksi is a simple, lightweight, and easy-to-use proxy server that automatically handles SSL, HTTP, and DNS traffic. It is designed to be used as a standalone proxy server or as a component in a larger system. Proksi is written in Rust and uses Pingora as its core networking library.
Features
- HTTPS: Automatic Redirect, SSL termination and certificate renewal (Let's Encrypt)
- Docker: Swarm/Compose Label Support
- Load Balancing (✅ Round Robin, ⛔︎ Weighted Round Robin, ⛔︎ Least Connections)
- HCL functions Extensible through configuration with
envandimport - Path matcher (regex, prefix and suffix)~ Pattern-based for high performance and flexibility
- Header manipulation for DOWNSTREAM & UPSTREAM (add/replace, remove)
- Health Checks
- Basic Authentication
- Oauth2 Authentication (Google, Facebook, ✅ Github, ✅ WorkOs etc)
- RequestId Middleware
Installation
Docker
Similar to other proxies, Proksi can be run as a Docker container. The following command will run the proxy server on ports 80 and 443, and mount the configuration file from the host machine to the container:
Binary
You can also run Proksi by downloading a binary from the Github Releases page for you platform. For example, for Ubuntu:
# Download the binary (change {VERSION} to the one you want)
Rust Cargo
You can also run Proksi using cargo:
Configuration
Proksi can be configured through HCL, YAML or Environment variables. See the examples folder to learn more about how to use Proksi using HCL or YAML.
Environment variables
Proksi can be configured using environment variables. They are mapped to the configuration file, always start with PROKSI_ and can be used to override the default values.
Example:
- For the key
worker_threads, the environment variablePROKSI_WORKER_THREADScan be used - For the key
logging.level, the environment variablePROKSI_LOGGING__LEVELcan be used (note the__separator due to the nested key) - For keys that accept a list of values, e.g.
routes, the environment variablePROKSI_ROUTEScan be used with a string value like this:
HCL (recommended)
Proksi can be configured using HCL (HashiCorp Configuration Language). This is the recommended way to configure Proksi, as it is more human-readable and easier to work with than JSON or YAML as well as it offers functions that you can use throughout your configuration:
worker_threads = env("WORKER_THREADS")
lets_encrypt {
enabled = true
email = env("LETS_ENCRYPT_EMAIL")
staging = true
}
paths {
lets_encrypt = env("LETS_ENCRYPT_PATH")
}
// You can split your websites into separate files
routes = [
import("./sites/mywebsite.com.hcl"),
import("./sites/myotherwebsite.co.uk.hcl")
]
// Or you can define them here
routes = [
{
host = "cdn.example.com"
ssl_certificate = {
// Useful for development
self_signed_on_failure = true
}
upstreams = [{
ip = "example.com"
port = 443
headers = {
add = [
{ name = "Host", value = "example.com" },
{ name = "X-Proxy-For", value = "cdn.example.com" }
]
}
}]
}
]
HCL functions
HCL supports functions that can be used to generate values in the configuration file. The following functions are supported:
env(name: string): Get the value of an environment variable. If the environment variable is not set, an error is thrown.import(path: string): Import another HCL file. The path is relative to the current file.
YAML
You can see below an excerpt of the configuration . This is still a work in progress, and the configuration format may change in the future.
# Example configuration file
worker_threads: 4
logging:
level: INFO
lets_encrypt:
enabled: true
# This issues temporary certificates for testing. Flip it to `false` to use
# production certificates.
staging: true
email: "your-email@example.com"
paths:
# where to store certificates?
lets_encrypt: "./my-lets_encrypt-folder"
routes:
- host: "example.com"
ssl_certificate:
# Useful for testing only
self_signed_on_failure: true
upstreams:
- ip: "10.1.2.24/24"
port: 3000
network: "public"
Documentation
For more tutorials, guides, and extended documentation, please refer to the https://docs.proksi.info.
Docker
Docker Labels
Proksi can be used in conjunction with Docker to automatically discover services and route traffic to them. To do this, you need to add labels to your Docker services (swarm or not).
Docker Swarm
# docker-compose.yml
# This is an example of how to use Proksi with Docker containers
# This will automatically discover services and route traffic to them
# based on the labels defined in the container.
networks:
web:
name: web
services:
# Proksi itself -- the only service that needs the `ports` directive
proksi:
environment:
PROKSI_LOGGING__LEVEL: "info"
PROKSI_WORKER_THREADS: 4
PROKSI_DOCKER__ENABLED: "true"
PROKSI_DOCKER__MODE: "swarm"
PROKSI_LETS_ENCRYPT__ENABLED: "true"
PROKSI_LETS_ENCRYPT__STAGING: "true"
PROKSI_LETS_ENCRYPT__EMAIL: "contact@your-website.com"
PROKSI_PATHS__LETS_ENCRYPT: "/etc/proksi/certs"
image: luizfonseca/proksi:latest
networks:
- web # Proksi needs to be in the same network as the services it will proxy
ports:
- "80:80"
- "443:443"
volumes:
# "data" folder should exist
- ./data:/etc/proksi/certs
# Your service
# This service will be automatically discovered by Proksi and doesn't need
# to expose any ports to the host, only to proksi
web:
image: nginxdemos/hello # This container exposes port 80
networks:
- web
deploy:
labels:
proksi.enabled: "true"
proksi.host: "myhost.example.com"
proksi.port: "80"
# you can make Proksi to use a self-signed certificate (in-memory)
proksi.ssl_certificate.self_signed_on_failure: "true"
# (Optional)
# E.g. `/api` will match only requests with "example.com/api*" to this service.
proksi.path.pattern.api: "/api"
# (Optional) Plugins
proksi.plugin.request_id.enabled: "true"
proksi.plugins.oauth2.provider: github
proksi.plugins.oauth2.client_id: <client_id>
proksi.plugins.oauth2.client_secret: <client_secret>
proksi.plugins.oauth2.jwt_secret: <jwt_secret> # The secret used to sign the JWT token
proksi.plugins.oauth2.validations: |
[ { "type": "email", "value": ["your-email@example.com"] } ]
Docker container
It's a very similar setup to the Docker Swarm, but the labels are defined outside of the deploy key and PROKSI_DOCKER__MODE is set to container:
services:
# proksi: ...
# Your service
# This service will be automatically discovered by Proksi and doesn't need
# to expose any ports to the host, only to proksi
web:
image: nginxdemos/hello # This container exposes port 80
networks:
- web
labels:
proksi.enabled: "true"
proksi.host: "myhost.example.com"
proksi.port: "80"
# ... other labels
Batteries included
Proksi is designed to be a standalone proxy server that can be used out of the box. It comes with a series of features that would normally require multiple tools to achieve. The vision of this project is to also cover most of the basic and extended requirements for a LB/Proxy server without getting into "convoluted" configurations.
The following features are included or will be included into Proksi without the need of 3rd party plugins:
- Basic Authentication
- Oauth2 Authentication (Google, Facebook, ✅ Github, ✅ WorkOs etc)
- RequestId Middleware
- Rate Limiting Middleware
- Geofence
- IP range blocking
- IP allowlists/denylists
- Others
Note that as always, these are mostly opt-in and are disabled by default.
Performance & Benchmarks
Static
Early tests are promising, but we need to do more testing to see how Proksi performs under real load. There are also some optimizations that can be done to improve performance in the long term, though the focus is on making it feature complete first.
An sample run from the wrk benchmark on the simple /ping endpoint shows the following results (running on a single worker thread):
# Apple M1 Pro, 16GB
# Memory Usage: 15.2MB, CPU Usage: 13%, 1 worker thread
# at 2024-06-13
# Wrk > 50 connections, 4 threads, 30s duration
It's also based on Pingora, so it should be fast if cloudflare is using it.
CDN (Disk + Memory)
There's multiple variations on performance, but you can see below that Proksi is able to handle a fair amount of traffic and is able to serve requests without major issues when network performance is involved:.
Specs: 4000 clients, 1 minute test, cache_type set to disk, Oracle server.
Why build another proxy...?
Many reasons, but the main one is that I wanted to learn more about how proxies work, and I wanted to build something that I could use in my own projects. I also wanted to build something that was easy to use and configure, and that could be extended with custom plugins AND offered out-of-the-box things that other projects needed the community to make.
Also, Rust is a very good use case for proxies (lower CPU usage, lower memory usage etc) and Cloudflare Pingora is basically a mold that you can create things with.
This project will be used in my personal experiments and I welcome you to explore and contribute as you wish.