Русская версия · Project overview
MegaProxyServer provisions HTTPS and SSH proxies on multiple Debian or Ubuntu servers. The Python CLI validates one inventory and produces Ansible variables, client configurations and a compact credential summary. Ansible installs and maintains the server-side components.
HTTPS is provided by GOST in a pinned container. HAProxy routes TLS by SNI when one entry server publishes several chain hostnames. SSH proxy users are restricted to local TCP forwarding and cannot open a shell, TTY, subsystem, remote forwarding or tunnel device.
The control machine needs macOS or Linux, Git, uv, OpenSSH and Python 3.12+. Managed machines need a
current Debian or Ubuntu release and a sudo-capable SSH account. Open TCP 443 for HTTPS and TCP 80
during standalone ACME validation in both the provider firewall and the host firewall.
Public-IP ACME certificates require Certbot 5.4+ and Let's Encrypt's short-lived profile.
Connect to Debian/Ubuntu with an interactive terminal (ssh -t) or use the server console.
You need root or sudo, curl and internet access. Install curl if necessary:
sudo apt-get update && sudo apt-get install -y curl ca-certificatesStart the installer (omit sudo when logged in as root):
curl -fsSL https://raw.githubusercontent.com/andre487/MegaProxyServer/main/install.sh | sudo bashTo inspect it first, download with curl -fsSL URL -o install.sh, review the file and run
sudo bash install.sh, using the URL above.
- Confirm dependency installation. The checkout lives in
/opt/megaproxy-server; Python and project dependencies are installed automatically. - Enter a host name, its public IP or DNS name, administrator and current SSH port. Choose or generate an administrative key, then select HTTPS and/or SSH, certificates and proxy users. For domain ACME, point DNS at this server beforehand. Open the required provider firewall ports.
- Set and save an Ansible Vault password; subsequent invocations will need it.
- Copy the displayed administrative private key to your computer before applying. With existing
root access, use another local terminal:
scp root@SERVER:/PATH/TO/KEY ./megaproxy-admin, thenchmod 600 ./megaproxy-admin. With a sudo account, transfer securely using sudo to read the file; do not make it publicly readable on the server. - Confirm that the key is saved. Setup creates the administrator locally, checks a fresh SSH login and sudo through loopback, disables root/password administrative login, configures services and the host firewall, verifies provisioning and exports client configurations.
- Keep the original session open and test login from your computer:
ssh -i ./megaproxy-admin -p PORT ADMIN@SERVER. Securely retrieve/opt/megaproxy-server/.generated/configs/MegaProxy.jsonusing sudo access.
If you cancelled before applying or setup failed, rerun the installer or resume with:
sudo /opt/megaproxy-server/mega-proxy setup-localExisting inventory is reused; the installer does not automatically update an existing checkout.
For maintenance, run sudo /opt/megaproxy-server/mega-proxy with the desired command.
Inventory, keys and exports stay on the server; keep private backups. In this mode local: true
routes administrative SSH through loopback while client exports retain the public address.
Only use this inventory on that server. To move control elsewhere, remove local: true and update
key paths. Local setup handles one server; multi-host management is described below.
git clone https://github.com/andre487/MegaProxyServer.git
cd MegaProxyServer
./mega-proxy inventory
./mega-proxy bootstrap
./mega-proxy plan
./mega-proxy apply
./mega-proxy verifyThe wizard can create administrative keys, proxy credentials and an encrypted inventory. An
inventory outside the repository is remembered in the ignored inventory-path file. Override it
at any time with --inventory PATH.
The wizard supports an initial password or a separate SSH key. Bootstrap prompts for the password
without echoing it. The password is kept only in a private temporary connection file, removed after
Ansible exits. Verify the server fingerprint and add its key to known_hosts first. Load
passphrase-protected administrative keys with ssh-add; deriving or validating the public key
also uses ssh-keygen -y, which can ask for the passphrase separately. Unattended apply requires
this step to work without an interactive prompt.
bootstrap creates the administrator and sudo access, checks a fresh key-only login and sudo -n,
then disables root and password-based administrative login. It verifies access again after SSH
reload and immediately updates inventory per host. SSH proxy users keep password authentication.
Retrying uses the administrator if already accessible, without requesting the initial password.
Keep the original SSH session open until verification succeeds. apply automatically bootstraps
selected pending hosts, including when tags are specified. Run bootstrap before planning new hosts.
To add a server while preserving existing settings and proxy users:
./mega-proxy add-host
./mega-proxy bootstrap --limit proxy_new
./mega-proxy plan --limit proxy_new
./mega-proxy apply --limit proxy_newBootstrap accepts comma-separated exact inventory host names for --limit; the flag can be repeated.
The fresh login check respects local SSH configuration and reports SSH or sudo errors. A later proxy
installation failure does not switch completed hosts back to root.
Use ./mega-proxy --help and ./mega-proxy COMMAND --help for commands and options.
Pass the global inventory option before the command:
./mega-proxy --inventory /secure/inventory.yml validate
./mega-proxy --inventory /secure/inventory.yml apply --limit proxy_eu --tags httpsInventory lookup uses the explicit path, then inventory-path, then inventory.yml. The launcher
runs from the repository root, so relative paths are resolved there. --no-hooks can appear before
or after the command.
Start with inventory.example.yml, replace the example addresses and keys, and validate before applying. For a server-side HTTPS chain, use the complete two-host inventory.chains.example.yml. Accepted fields, validation rules and defaults are defined in models.py; container versions are pinned there and can be overridden in inventory.
Proxy users live in the global users section and apply to every host with that service enabled.
Use dedicated SSH proxy logins, separate from the administrator. SSH key exports read the file at
generated_private_key on the control machine and embed its contents; a public key alone is not
enough to generate a client configuration. Android MegaProxy requires an unencrypted private key.
To link SSH accounts explicitly, set ssh_users: [tun-alice] on an HTTPS user or run
./mega-proxy link-users to choose existing accounts. The wizard offers this selection when
creating users of both types. An empty or omitted ssh_users means no links; names are never
matched automatically. Unknown accounts and duplicates fail inventory validation. An SSH account
may be linked to multiple HTTPS users; removing an SSH account also removes its links. These links
provide metadata for personalized delivery; the current export and configs commands still export all users.
services.ssh.enabled defaults to true and controls SSH proxy provisioning and verification.
services.ssh.generate_profile defaults to false and selects hosts for direct SSH profiles in
export, configs and the configuration API. Administrative SSH uses admin settings separately.
The wizard asks whether to publish direct SSH profiles, defaulting to yes; omitting the field
in hand-written YAML still defaults to false. enabled: false also
disables an existing proxy: HTTPS stops GOST, HAProxy and the renewal timer; SSH
denies the megaproxy group and closes its sessions while preserving administrative
SSH. Keep the disabled service in YAML: an absent service is unmanaged. Firewall
records its own rules and revokes retired ports on the next full apply, preserving
other rules. An initial upgrade records existing configured ports before later changes;
older open ports already absent from inventory are not discovered automatically.
Only accounts whose primary group is megaproxy may be modified or removed;
new proxy accounts are created in that group. Administrative/bootstrap accounts,
reserved names and paths are rejected in removed_ssh. The administrative
authorized_keys file is managed exclusively: replacing admin.public_key
revokes the old key. Changing admin.user requires bootstrap through a working
account: the new administrator is verified before retiring the previous managed
account and sudo access, preserving its files.
For key rotation, set the new public_key/private_key_file, the current admin as
bootstrap_user, and the old private key as bootstrap_private_key_file.
Bootstrap verifies the new login before revoking the old key; mismatched public
and private keys are rejected.
Inventory and exports are replaced atomically with mode 0600. Export validation
runs before replacing an existing file; country codes outside [A-Z]{2} become empty strings.
Port conflicts and HTTPS without routes are rejected before connecting to a host.
Docker cleanup only removes old GOST/Certbot images, without global prune operations.
HTTPS chains verify the exit certificate and hostname. CI uses a local CA to test
knock/chain overrides, nested TLS Jump, and rejection of bad credentials, certificates
or unavailable exits, without external domains or ACME for these checks.
| enabled | generate_profile | Result |
|---|---|---|
| true | true | Provision SSH proxy users and publish direct SSH profiles |
| true | false | Provision SSH proxy users; use the host only in declared jump pairs |
| false | any | Deny proxy access, close proxy sessions and omit profiles; jump pairs cannot reference this host |
Disabling SSH retains proxy accounts but denies their access; it does not stop the server's SSH
daemon. To delete an account, use remove-users and apply. A tagged apply must include
firewall to reconcile ports, for example apply --tags ssh,https,firewall.
hosts:
de_proxy:
# address and admin as in inventory.example.yml
services:
ssh:
title: DE
enabled: true
generate_profile: trueDirect SSH titles use the required services.ssh.title field.
The API omits the login; file exports append / login, as for HTTPS.
settings:
ssh_jump_pairs:
- entry: de_proxy
exit: us_proxy
title: SSH DE -> US
country_code: US
https_jump_pairs:
- entry: de_proxy
exit: us_proxy
title: HTTPS DE -> US
country_code: USentry is the first hop; exit is the destination. SSH Jump uses the same account from
users.ssh on both hops. export and configs generate one profile per SSH account;
the API uses only accounts linked through the HTTPS user's ssh_users. No per-hop login
is configured in a pair. Both SSH services must be enabled, but generate_profile may be false.
HTTPS Jump uses the current HTTPS user's credentials on both hops and requires two enabled
direct HTTPS endpoints. It does not require https_chains_enabled or a chain domain.
For both jump lists, title is required and country_code is optional.
The country defaults to the exit's code. An explicit country must
be two uppercase letters. Empty lists create no jump profiles; no combinations are inferred.
Unknown hosts, disabled services, repeated pairs and identical entry/exit hosts fail validation.
./mega-proxy jumps lists configured SSH pairs for each account. The legacy --all-jumps
export flag is accepted but never enables undeclared pairs.
See inventory.jumps.example.yml for a complete example.
https_chain_pairs is separate: it creates server-side SNI routes with individual hostnames,
exported as ordinary HTTPS profiles. See inventory.chains.example.yml.
The API and MegaProxy JSON list direct HTTPS, direct SSH, https_chain_pairs,
https_jump_pairs, then ssh_jump_pairs. Direct profiles follow inventory host order;
pairs follow their respective list order. MASQUE and SOCKS5 belong to the first group.
Reordering preserves IDs. FoxyProxy, SuperProxy and ProxyList list direct HTTPS routes
first, then server-side chains.
HTTPS and SSH services and all chain/jump pairs require a nonblank title.
Names are read directly from inventory; no names are derived from host identifiers.
Direct HTTPS country codes come from the first segment of the host identifier after removing
an optional proxy_ prefix: de_entry and proxy_de yield DE. Identifiers such as px-de
yield an empty code. Codes must match two uppercase letters; no country registry is consulted.
Explicit HTTPS chain pairs require country_code; jump pairs can override the exit's inferred
code. Direct SSH and SOCKS5 profiles currently have an empty country code.
The code is a display hint; use the client connection test to check the observed exit country.
HTTPS and jump profile IDs use logical host/route identifiers and the account, so changing a
title or endpoint preserves those IDs. Direct SSH IDs also use the host and account. SOCKS5 exports still derive IDs from their names.
https_chain_pairs creates only declared entry/exit routes; an empty list creates no server-side
chains. Each exit needs separate machine credentials in
chain_password, which are excluded from client exports. Chain titles use the required pair title.
Set a knock host per HTTPS proxy using services.https.probe_resistance.knock:
probe_resistance:
enabled: true
mode: local_decoy
knock: [knock.example.com]When probe resistance is enabled, the first host becomes browser.knockHost in API and MegaProxy file profiles.
Chain routes inherit the entry setting unless the pair overrides probe_resistance.
The setup wizard asks for the knock hostname when probe resistance is enabled.
Apply HTTPS settings to the proxy hosts and update the config API bundle after changing it.
MASQUE profiles do not use knock hosts.
Probe resistance suppresses the normal authentication challenge and can break browser clients
that wait for 407 before sending credentials. Keep it disabled unless tested with the required
clients. A route can override the entry setting; knock allows selected hostnames to receive the
normal challenge.
Masked routes serve a static site using GOST's built-in loopback file server. A standard-library
Python script generates a random heading and abstract SVG once. Repeated apply runs keep the
site; remove /opt/megaproxy/decoy/index.html to regenerate it on the next apply.
The page includes noindex,nofollow; /robots.txt contains User-agent: * and Disallow: /.
Only enabled and knock currently affect masking. The inventory model accepts mode,
status_code and site_title, but they do not select another response mode
or control the generated site's title.
Domain certificates are issued with Certbot standalone ACME. An entry certificate contains its direct endpoint, when enabled, and all configured chain hostnames. After a pair is added, the role detects a missing SAN, expands the certificate and restarts GOST to load it.
Every chain hostname must resolve to its entry server before apply. HAProxy reads SNI without
terminating TLS and forwards the connection to a dedicated loopback GOST HTTP/2 listener. The entry
then connects to the selected exit with machine credentials. A pair in https_chain_pairs may
override probe_resistance for that chain route only.
Certificate renewal runs from a systemd timer. Self-signed mode is an explicit fallback and requires clients to allow an invalid proxy certificate.
./mega-proxy configs
./mega-proxy configs --output-dir /secure/pathThe default .generated/configs directory contains:
MegaProxy.json: HTTPS, enabled MASQUE/SOCKS5 transports, direct SSH for hosts withservices.ssh.generate_profile: true, and explicitly configured SSH/HTTPS jump profiles;FoxyProxy.json: HTTPS profiles;SuperProxy.txt: HTTPS URLs with the Super Proxy v1 header;ProxyList.txt: URL-encoded HTTPS proxy URLs.
Directories use mode 0700 and files 0600. Generation is idempotent and does not contact managed
servers. Exports contain plaintext passwords and private keys; handle them as secrets.
Optional services.config_api deploys personalized HTTPS configuration feeds to separate,
equal peers. See the inventory example and
configuration API guide (Russian). Feeds use canonical v8, scrypt access
verification, password-encrypted SSH secrets, client projections, bootstrap subscriptions and
authenticated ETags. /robots.txt is public; other paths and invalid credentials return 403.
The exporter writes schema net.megaproxy487.config
v7 for basic HTTPS/SSH profiles and v8 when https_jump_pairs, HTTP/3 or SOCKS5 is configured.
MASQUE profiles require HTTP/3. The configuration API always returns canonical v8 and filters
profiles for the requested client; browser projections omit SSH, jump and MASQUE profiles.
-
Install MegaProxy using the client installation guide.
-
Run
./mega-proxy configsand privately transferMegaProxy.jsonto the device. The app can also import the generated FoxyProxy, SuperProxy and ProxyList files, but those contain HTTPS routes only. Prefer MegaProxy JSON to retain SSH profiles, IDs and certificate settings. -
Import the file and select the intended profile. Review routing, DNS and failover settings.
-
Choose Test from the main-screen menu. For SSH, compare the displayed host-key fingerprint with one obtained through the server console or an already trusted administrative connection. SSH jump profiles require verification of both hosts. On the server, inspect public host keys with:
for key in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf "$key" -E sha256; done
-
Connect and approve Android's VPN permission. The app test checks the route and observed exit IP;
./mega-proxy verifychecks server deployment and does not replace this device test.
Exports leave SSH trust fingerprints empty and keep host-key checking enabled. Self-signed HTTPS routes explicitly allow an invalid proxy certificate in MegaProxy JSON; the other export formats cannot carry that setting. Prefer ACME certificates for normal operation.
Repeat configs after server or credential changes, then reimport the JSON. Matching IDs update
existing profiles; new IDs add profiles. Generated HTTPS, MASQUE and jump IDs depend on logical
host/route identifiers and accounts; changing their title, country or endpoint preserves IDs.
Renaming an inventory host or account changes those IDs. Direct SSH IDs also use logical host/account identifiers. SOCKS5 exports derive
IDs from names; renaming those profiles changes IDs. The API uses logical identities for all
generated profiles. Additional manual profiles must supply unique IDs.
The app offers optional removal of local profiles missing from the file; they are not deleted
automatically. Removing a profile on the phone does not revoke access: use remove-users and
apply to revoke server credentials.
The client preserves stored passwords and private keys when their JSON fields are omitted; explicit empty fields clear them. This generator includes credentials, so reimport replaces them. It also imports global connection settings, including routing and disabled failover, and replaces profile settings. Review device customizations after importing. Because generated SSH trust fields are empty, reimport can require host-key confirmation again.
| Route | Setup | What the phone imports |
|---|---|---|
| Server-side SNI chain | Configure entry/exit and DNS using the chain inventory example, then apply | An ordinary HTTPS profile pointing to the entry's chain hostname; GOST selects the exit |
| Client-side HTTPS with Jump | Provision two direct HTTPS endpoints and configure settings.https_jump_pairs |
A HTTPS_JUMP profile in JSON v8 |
For HTTPS with Jump, enter the exit in the main proxy fields and the entry in Jump HTTPS
proxy. Use global HTTPS user credentials on each hop; machine chain_password credentials belong
to server-side chains. The entry must resolve and allow CONNECT to the exit hostname and port.
Each hop verifies its own certificate. This mode does not require a chain domain or server-side
https_chains_enabled. A failed hop does not cause a direct fallback to the exit.
See the client's HTTPS with Jump guide.
Client-side HTTPS jump profiles require a v8-capable app; older clients reject v8 files. Server-side
SNI routes remain usable through the HTTPS-only export formats. SSH jump profiles are generated by
configs and export from settings.ssh_jump_pairs. Both jump lists default to empty;
--all-jumps is a compatibility flag and never generates undeclared combinations.
HTTPS and SSH forward TCP; MASQUE and SOCKS5 also forward UDP in compatible Android builds. Availability depends on the installed client release; see its transport guide. SSH keys protected by a passphrase are unsupported. DNS, per-app routing, failover and Android Always-on VPN are configured in the app. Exported profiles do not enable failover.
Use Test to distinguish proxy setup, destination access and exit-IP failures. It contacts external test services through the proxy; proxy-hostname bootstrap may query external DoH providers directly before the tunnel exists. Diagnostic logs stay on the device unless shared explicitly. See the client's privacy policy for destinations and handling of shared reports. Review reports before sharing and keep generated credentials out of issue trackers.
./mega-proxy summary prints endpoints once and then each global credential once:
Hosts:
proxy-eu.example.com (EU, HTTPS, SSH)
eu-via-us.example.com (EU -> US, HTTPS)
HTTPS:
alice
PASSWORD
SSH:
tun-alice
/path/to/private/key
summary alice selects an exact proxy login. Administrative users are omitted. Never write this
output to an unprotected file or CI log.
remove-users marks users for removal in inventory; run apply to remove HTTPS users from GOST. Removed SSH users also enter the
global removed_ssh tombstone list; the next apply deletes their system accounts and root-owned
authorized-key files on every SSH host. The last user of an enabled service cannot be removed.
vault-secrets encrypts individual secrets as tagged Ansible Vault values while leaving hosts,
ports and flags readable. Existing encryption is preserved. Set ANSIBLE_VAULT_PASSWORD_FILE for
unattended use; otherwise the CLI prompts for a password.
Whenever encrypted inventory is saved, the CLI encrypts all secret fields again, including
unchanged values. Ansible Vault uses a random salt, so every !vault block may change in the diff
while the passwords themselves remain unchanged. For example, bootstrap saves inventory after
each successfully configured host when switching to the permanent administrator. This is a current
limitation of saving: original encrypted blocks are not retained for unchanged secrets. These
changes alone do not indicate password rotation on the servers or require updating client credentials.
After a successful apply, executable .hooks/post-config-change and files in
.hooks/post-config-change.d/ run in name order. They receive MEGAPROXY_INVENTORY,
MEGAPROXY_ANSIBLE_INVENTORY, MEGAPROXY_ROOT and MEGAPROXY_EVENT. The ignored .hooks
directory is suitable for machine-local integrations such as px-manager.
- Treat inventory,
.secrets,.generated, summaries and exports as secrets. - Use one proxy account/key per person or device for independent revocation.
- Verify SSH fingerprints through a trusted channel.
- Root SSH login is disabled; the permanent administrator is key-only and has sudo.
- Provider firewall rules remain the operator's responsibility.
- Optional probe resistance hides the protocol from unauthenticated HTTP probes, not the server IP or SNI; enable it only after testing the required clients.
./mega-proxy check runs Ruff, pytest, byte-compilation and Ansible syntax checks. CI runs this
suite on Python 3.12 and 3.14. Separate integration jobs run real GOST and nginx on loopback
and provision Ubuntu 24.04 plus two Debian 12 LXD guests. They cover SSH Jump, credential
rotation, account removal, proxy disable/re-enable, firewall cleanup and administrative access.
They use temporary credentials and self-signed or local-CA certificates; they do not test
production DNS, public ACME issuance or an Android device. These jobs are not run by check.
main requires a pull request, all five CI checks, an up-to-date branch and resolved
conversations. Force pushes and deletion are forbidden, including for administrators;
approval by a second person is not required. The required check names match the workflows.
- ACME fails: check DNS, TCP 80 and provider firewall rules.
- A chain domain serves an old certificate: run
apply --limit ENTRY --tags https, then inspect certificate SANs andmegaproxy-goststatus. - Unexpected inventory: pass
--inventory PATHor inspectinventory-path. - px-manager did not update: ensure the post-change hook is executable and
--no-hooksis absent.