Jaeger 2.22.0
Overview
Jaeger is an open-source distributed tracing platform, a graduated project of the Cloud Native Computing Foundation. Your services send it OpenTelemetry (OTLP) traces. Jaeger stores them and shows how one request travels through your system: which services it touched, how long each step took and where errors happened. Jaeger 2 is built on the OpenTelemetry Collector.
This guide covers launching and using the Jaeger 2.22.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud tracing and APM listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
What you get:
- No default login, for people and for applications. Jaeger has no authentication of its own, so this image never exposes it. nginx serves the web UI, the query API and OTLP/HTTP trace ingest over HTTPS with HTTP basic authentication. The password is generated on your instance's first boot and written to
/root/jaeger-credentials.txt(root-only). The image ships no password, no password hash and no TLS key. - Jaeger on localhost only. Every port Jaeger opens (16686 query and web UI, 16685 query gRPC, 4317 OTLP gRPC, 4318 OTLP HTTP, 13133 health) is bound to
127.0.0.1. nginx on port 443 is the only way in. The unauthenticated/healthzpath answersokfor load-balancer health checks and reveals nothing else. - HTTPS from the first boot. The certificate is generated on your instance for its own addresses, port 80 redirects to HTTPS, and requests for a host name that the instance does not serve are dropped. Cross-site posts and WebSocket upgrades from other websites are refused.
- Built from source with a patched toolchain. SecureAnyCloud compiles Jaeger 2.22.0 from the upstream source with Go 1.27.3 and a patched golang.org/x/net, so govulncheck finds no reachable vulnerability in the binary (the upstream release binary was built with Go 1.27.1). The build record, with every checksum, is in /usr/share/doc/jaeger/BUILD-PROVENANCE.txt. No compiler or Go toolchain is left on the image.
- Fail closed. nginx and Jaeger start only after first boot has finished. If it cannot finish, nothing is served.
- Nothing sent home, no self-telemetry. Jaeger does not trace itself into your store and does not serve its own metrics endpoint, so the database holds only the traces you send it and the instance makes no outbound calls.
- No metadata access.
jaeger.service,nginx.serviceand the first-boot service are blocked from the instance metadata service, and the AMI requires IMDSv2. - Honest client addresses. nginx hands Jaeger a fixed host name and the real client address from the connection, and never forwards headers that a client sends.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Jaeger 2.22.0 (one binary with the OTLP receiver, the query service and the web UI) storing traces in an embedded Badger database on the local disk, nginx 1.24 and OpenSSL 3.0 (from Ubuntu 24.04), cloud-init and the AWS Systems Manager agent.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route that you use for management).
- A security group that allows TCP 22 (SSH) from your admin network and TCP 443 (web UI and trace ingest) only from the people and the applications or collectors that send traces. Port 80 only redirects to HTTPS, so you can leave it closed. Never open 16686, 16685, 4317, 4318 or 13133, because Jaeger listens on the loopback address only.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Jaeger 2.22.0 on Ubuntu 24.04 as the software version, choose your Region, then choose Launch through EC2. Choose m5.large, your key pair and a security group as above.
Traces are stored on the root volume under /var/lib/jaeger/data, so give it room for your traffic and retention (48 hours by default). 30 GiB or more is a sensible start.
To launch with the AWS CLI instead, replace the values in angle brackets:
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=30,VolumeType=gp3}' \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=jaeger}]'First boot takes a few seconds after the instance is running. Jaeger is ready a few seconds after that.
Connect to your instance
Connect over SSH as ubuntu, the default SSH user for Jaeger 2.22.0 on Ubuntu 24.04:
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot sets the password and the certificate, then nginx and Jaeger start. Check the three units and the first-boot marker:
systemctl is-active jaeger nginx jaeger-firstboot
sudo test -f /var/lib/sac/data/jaeger-firstboot.done && echo FIRSTBOOT_DONEAll three units are active and the first-boot marker is present:
active active active FIRSTBOOT_DONEOnly SSH and nginx listen on network ports. Everything Jaeger opens is on the loopback address:
sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort0.0.0.0:22 users:(("sshd"),("systemd")) 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"),("nginx"),("nginx")) 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"),("nginx"),("nginx")) 127.0.0.1:13133 users:(("jaeger")) 127.0.0.1:16685 users:(("jaeger")) 127.0.0.1:16686 users:(("jaeger")) 127.0.0.1:4317 users:(("jaeger")) 127.0.0.1:4318 users:(("jaeger")) [::]:22 users:(("sshd"),("systemd")) [::]:443 users:(("nginx"),("nginx"),("nginx"),("nginx"),("nginx")) [::]:80 users:(("nginx"),("nginx"),("nginx"),("nginx"),("nginx"))Query Jaeger's own health endpoint, on the loopback address:
curl -s http://127.0.0.1:13133/status | jq -r .statusStatusOKGet your URL and password
Read the credentials file:
sudo cat /root/jaeger-credentials.txtThe file is readable by root only. It contains:
# Jaeger 2.22.0 on Ubuntu 24.04 (sac) - generated on this instance's first boot. # This password is unique to this instance; none was baked into the image. Keep this file secret. JAEGER_URL=https://<public-ip>/ JAEGER_USER=admin JAEGER_PASSWORD=<JAEGER_PASSWORD> OTLP_HTTP_TRACES_URL=https://<public-ip>/v1/traces # Web UI + query API: https://<host>/ (HTTP basic auth with the user and password above) # Trace ingest: OTLP/HTTP (protobuf or JSON) POST to https://<host>/v1/traces with the SAME basic auth, e.g. # OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://<host>/v1/traces # OTEL_EXPORTER_OTLP_TRACES_HEADERS="Authorization=Basic <base64 of admin:password>" # OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE=<a copy of /etc/nginx/tls/jaeger.crt> # OTLP gRPC (4317) and HTTP (4318) listen on 127.0.0.1 only: use an SSH tunnel or a collector on this instance. # Health: https://<host>/healthz (no login; answers "ok") # The certificate is self signed (minted on this instance): accept the browser warning once, give senders the # certificate, or install your own. Serve a DNS name instead:
sudo jaeger-set-hostname <your-dns-name>
# Data (Badger): /var/lib/jaeger/data Self-test:
sudo /usr/local/sbin/jaeger-selftest.sh
# User guide: https://secureanycloud.com/stacks/jaeger-2-22-0/The URL uses the instance's public IP address from the EC2 instance metadata. The certificate also names the public DNS name and the private IP address, so https://<public-dns-name>/ and https://<private-ip>/ work too.
If you stop and start the instance without an Elastic IP, the boot service picks up the new address and issues a new certificate for it. To serve Jaeger under your own DNS name, see Step 9.
A request without the password gets nginx's 401, and the health check answers without one:
curl -sk -o /dev/null -w '%{http_code}\n' https://127.0.0.1/
curl -sk https://127.0.0.1/healthz401 okSend a first trace
Applications send traces with OTLP over HTTP to https://<host>/v1/traces, with the user admin and the password from the credentials file. To see it work on the instance, send one example trace of a checkout request that passes through three services (frontend, shop-api and payments, seven spans), in OTLP's JSON encoding.
The small shell function jg passes the password from the credentials file to curl on standard input, so the password never appears on a command line:
jg() {
sudo sed -n 's/^JAEGER_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/jaeger-credentials.txt | curl -sgk -K - "$@"; }
TRACE=$(openssl rand -hex 16);
T0=$(( $(date +%s) - 5 )) ts() { printf '%s%09d' "${T0}" $(( $1 * 1000000 )); } span() {
# id parent name kind start_ms end_ms attributes-json printf '{"traceId":"%s","spanId":"%s","parentSpanId":"%s","name":"%s","kind":%s,"startTimeUnixNano":"%s","endTimeUnixNano":"%s","attributes":[%s]}' \
"${TRACE}" "$1" "$2" "$3" "$4" "$(ts "$5")" "$(ts "$6")" "$7"; } attr() { printf '{"key":"%s","value":{"stringValue":"%s"}}' "$1" "$2"; } svc() { printf '{"resource":{"attributes":[%s]},"scopeSpans":[{"scope":{"name":"demo"},"spans":[%s]}]}' "$(attr service.name "$1")" "$2"; } FRONT="$(span 1000000000000001 "" "GET /checkout" 2 0 420 "$(attr http.route /checkout),$(attr http.request.method GET)"),$(span 1000000000000002 1000000000000001 "POST /api/orders" 3 20 400 "$(attr server.address shop-api)")" SHOP="$(span 2000000000000001 1000000000000002 "POST /api/orders" 2 30 390 "$(attr http.route /api/orders)"),$(span 2000000000000002 2000000000000001 "SELECT orders" 3 40 160 "$(attr db.system.name postgresql),$(attr db.operation.name SELECT)"),$(span 2000000000000003 2000000000000001 "POST /charge" 3 170 380 "$(attr server.address payments)")" PAY="$(span 3000000000000001 2000000000000003 "POST /charge" 2 180 375 "$(attr http.route /charge)"),$(span 3000000000000002 3000000000000001 "card-network authorize" 3 200 360 "$(attr peer.service card-network)")" printf '{"resourceSpans":[%s,%s,%s]}' "$(svc frontend "${FRONT}")" "$(svc shop-api "${SHOP}")" "$(svc payments "${PAY}")" > /tmp/demo-trace.json jg -X POST -H 'Content-Type: application/json' --data-binary @/tmp/demo-trace.json https://127.0.0.1/v1/traces; echo echo "trace id: ${TRACE}"Jaeger accepts the whole trace. An empty partialSuccess means no span was rejected:
{"partialSuccess":{}} trace id: 85ebcfe31dbcdf5c6d81179a713e86b1The same upload without the password is refused by nginx before it reaches Jaeger:
curl -sk -o /dev/null -w '%{http_code}\n' -X POST -H 'Content-Type: application/json' --data-binary @/tmp/demo-trace.json https://127.0.0.1/v1/traces401Explore the traces
Ask Jaeger which services and operations it has seen, through the query API (the same API the web UI uses):
jg() {
sudo sed -n 's/^JAEGER_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/jaeger-credentials.txt | curl -sgk -K - "$@"; } jg https://127.0.0.1/api/v3/services; echo jg "https://127.0.0.1/api/v3/operations?service=shop-api" | jq -c '[.operations[].name]'{"services":["frontend","payments","shop-api"]} ["POST /api/orders","POST /charge","SELECT orders"]Search for the traces that touched payments in the last hour and list their spans:
jg() {
sudo sed -n 's/^JAEGER_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/jaeger-credentials.txt | curl -sgk -K - "$@"; } jg -G https://127.0.0.1/api/v3/traces --data-urlencode query.service_name=payments \
--data-urlencode "query.start_time_min=$(date -u -d '-1 hour' +%Y-%m-%dT%H:%M:%SZ)" \
--data-urlencode "query.start_time_max=$(date -u -d '+1 minute' +%Y-%m-%dT%H:%M:%SZ)" --data-urlencode query.search_depth=1 \
| jq -r '.result.resourceSpans[] | (.resource.attributes[] | select(.key=="service.name") | .value.stringValue) as $svc | .scopeSpans[].spans[] | "\($svc): \(.name)"' | sort -ufrontend: GET /checkout frontend: POST /api/orders payments: POST /charge payments: card-network authorize shop-api: POST /api/orders shop-api: POST /charge shop-api: SELECT ordersJaeger also derives the service dependency graph from the traces:
jg() {
sudo sed -n 's/^JAEGER_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/jaeger-credentials.txt | curl -sgk -K - "$@"; } jg -G https://127.0.0.1/api/dependencies --data-urlencode endTs=$(( $(date +%s) * 1000 )) --data-urlencode lookback=3600000 | jq -c '.data'[{"parent":"frontend","child":"shop-api","callCount":1},{"parent":"shop-api","child":"payments","callCount":1}]In the browser, open the URL from the credentials file. Your browser warns once about the self-signed certificate. Accept the warning, or install your own certificate (Step 9). Then sign in as admin with the password from the credentials file.
On Search, pick a service (here frontend) and choose Find Traces. The chart shows each trace's duration over time, and the table lists the traces with their services and span counts.
Select a trace to open its timeline. Each row is a span, indented under its parent, and the bars show when it ran and for how long. Here the database query and the card network call are the two longest steps inside shop-api and payments.
System Architecture draws the dependency graph between your services from the traces it has stored.
The example data is yours to delete. It ages out after 48 hours. For a clean store sooner, stop Jaeger, empty /var/lib/jaeger/data/badger and start it again.
Send traces from your applications
Point your OpenTelemetry SDKs or your OpenTelemetry Collector at https://<host>/v1/traces (the private IP or your DNS name, which the certificate names) with basic authentication.
Copy the instance's certificate (/etc/nginx/tls/jaeger.crt) to the senders so that they can verify it, or install a certificate that your systems already trust (Step 9).
Any OpenTelemetry SDK (Java, Python, Go, Node.js, .NET and others) reads the standard environment variables. The header value is Basic followed by the base64 of admin:<password>, with the space written as %20:
OTEL_SERVICE_NAME=my-service OTEL_TRACES_EXPORTER=otlp OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://<host>/v1/traces OTEL_EXPORTER_OTLP_TRACES_HEADERS=Authorization=Basic%20<base64 of admin:password> OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE=/etc/ssl/jaeger.crtCompute the base64 value on the instance without the password appearing on a command line:
sudo sed -n 's/^JAEGER_PASSWORD=//p' /root/jaeger-credentials.txt | tr -d '\n' | sed 's/^/admin:/' | base64 -w0; echoOpenTelemetry Collector (or Grafana Alloy's otelcol components): send traces with the otlphttp exporter and the basicauth extension:
extensions: basicauth/jaeger: client_auth: username: admin password: ${env:JAEGER_PASSWORD} exporters: otlphttp/jaeger: endpoint: https://<host> auth: authenticator: basicauth/jaeger tls: ca_file: /etc/otelcol/jaeger.crt service: extensions: [basicauth/jaeger] pipelines: traces: receivers: [otlp] exporters: [otlphttp/jaeger]OTLP gRPC (port 4317) is not exposed. To use it from your workstation, forward it over SSH and send to localhost:4317 without TLS:
ssh -i <your-key.pem> -N -L 4317:127.0.0.1:4317 ubuntu@<public-ip>Keep the password in a secrets store or in a file that only the sender can read, never in source code. The Jaeger documentation and the OpenTelemetry documentation cover instrumenting each language.
Storage and retention
Jaeger stores traces in an embedded Badger database on the local disk under /var/lib/jaeger/data/badger, with no separate database to run. It keeps traces for 48 hours. Traces that you archive from the web UI (Archive Trace) go to a second store, /var/lib/jaeger/data/badger-archive, which is kept for 30 days. The settings are in /etc/jaeger/config.yaml. Print the storage section:
sudo grep -A6 'sac_store:' /etc/jaeger/config.yaml | head -7sac_store: badger: directories: keys: /var/lib/jaeger/data/badger values: /var/lib/jaeger/data/badger ephemeral: false ttl:To keep traces for 7 days, change spans: 48h under sac_store to spans: 168h (sudo nano /etc/jaeger/config.yaml), then run sudo systemctl restart jaeger. Keep every 127.0.0.1 endpoint as it is.
Badger reserves its value log files in 2 GiB pieces. These are sparse files, so they use only the space their data needs.
For higher volumes or long retention, Jaeger can use Elasticsearch, OpenSearch or Cassandra instead (jaeger_storage backends; see the storage documentation). jaeger.service is blocked from the instance metadata service, so it cannot use an instance role. A remote store needs its own credentials.
Your own DNS name and certificate
Point a DNS record at the instance (an Elastic IP keeps the address stable), then set the name that Jaeger is served as. This command mints a new self-signed certificate for the name, updates nginx, the list of origins the web UI may post from and the URLs in /root/jaeger-credentials.txt, and keeps the name across reboots:
sudo jaeger-set-hostname <your-domain>To use a certificate from a public or internal certificate authority instead (for example one issued by Let's Encrypt for <your-domain>), copy it over the generated pair and reload nginx. Running jaeger-set-hostname again replaces it with a self-signed one:
sudo install -m 0644 <your-domain>.crt /etc/nginx/tls/jaeger.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/tls/jaeger.key
sudo nginx -t && sudo systemctl reload nginxSecurity checks
The shipped self-test checks that:
- the set of listening ports is exactly the expected one, and every Jaeger port is on the loopback address
- Jaeger holds no outbound connection
- an unknown host name is dropped
- requests to the web UI, the query API and trace ingest without a password, with
admin/adminor with a wrong password are refused by nginx, while this instance's password is accepted - a cross-site post or WebSocket upgrade is refused
- the instance metadata service cannot be reached from inside the
jaegerandnginxservices, while it can from your shell
The password is handed to curl in a root-only temporary file, never on a command line. Run the self-test:
sudo /usr/local/sbin/jaeger-selftest.shselftest OK: listening tcp lo:13133 lo:16685 lo:16686 lo:4317 lo:4318 lo:53 pub:22 pub:443 pub:80 | jaeger listeners 127.0.0.1:13133 127.0.0.1:16685 127.0.0.1:16686 127.0.0.1:4317 127.0.0.1:4318 (loopback only), no outbound connection; healthz ok, unknown Host dropped (444); anonymous UI+API+ingest 401, admin/admin and wrong passwords 401 (nginx auth), per-instance password 200 (UI + query API), cross-origin POST and WebSocket 403; instance metadata blocked inside jaeger and nginx (000), reachable from the host shell (200)Add --ingest to also send one span for a service called sac-selftest and read it back.
The metadata block is a systemd setting on each service. Show it:
systemctl show -p IPAddressDeny jaeger nginx jaeger-firstbootIPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32A request for a host name that the instance does not serve gets no answer at all. nginx closes the connection, so curl exits with code 52:
curl -sk -o /dev/null -w '%{http_code}\n' -H 'Host: attacker.example' https://127.0.0.1/healthz; echo "curl exit $?"000 curl exit 52The certificate was generated on this instance for its own addresses. Inspect it:
sudo openssl x509 -in /etc/nginx/tls/jaeger.crt -noout -subject -ext subjectAltNamesubject=CN = <public-ip>, O = sac X509v3 Subject Alternative Name: IP Address:<public-ip>, DNS:<public-dns-name>, IP Address:<private-ip>, DNS:<private-dns-name>, IP Address:127.0.0.1, DNS:localhostVersions, build record and files
Print the Jaeger and nginx versions and the build record summary:
/usr/local/bin/jaeger version 2>/dev/null; echo nginx -v 2>&1 grep -E '^(tag commit|toolchain |govulncheck)' /usr/share/doc/jaeger/BUILD-PROVENANCE.txt | cut -c1-110{"gitCommit":"462bb49fcff9af4be9556bf0206d07f5171bd8d7","gitVersion":"v2.22.0","buildDate":"2026-10-06T11:44:34Z"} nginx version: nginx/1.24.0 (Ubuntu) tag commit 462bb49fcff9af4be9556bf0206d07f5171bd8d7 toolchain go1.27.3 linux/amd64, official tarball https://go.dev/dl/go1.27.3.linux-amd64.tar.gz govulncheck v1.9.0 -mode=binary on this binary: Your code is affected by 0 vulnerabilities. This scan alThese are the files that matter:
- /usr/local/bin/jaeger: the Jaeger binary, run by jaeger.service as the jaeger user
- /etc/jaeger/config.yaml: Jaeger's settings, including receivers, storage and retention, and the loopback endpoints
- /var/lib/jaeger/data: trace storage (Badger) and the archive store
- /etc/nginx/sites-available/sac-jaeger: the nginx site, with HTTPS, basic authentication, and a proxy to 127.0.0.1:16686 and 127.0.0.1:4318
- /etc/nginx/jaeger.htpasswd: the basic authentication user and password hash (SHA-512 crypt)
- /etc/nginx/tls/: this instance's certificate and private key
- /root/jaeger-credentials.txt: URL, user and password (root-only)
- /usr/share/doc/jaeger/: the Apache License 2.0, Jaeger's NOTICE and the build record with the source offer
Changing the password
Run these lines in your SSH session. The new password is read at a prompt and reaches openssl on standard input, so it never appears on a command line or in your shell history:
read -rsp 'New password: ' NEWPASS; echo printf 'admin:%s\n' "$(printf '%s\n' "$NEWPASS" | openssl passwd -6 -stdin)" | sudo tee /etc/nginx/jaeger.htpasswd >/dev/null unset NEWPASSnginx reads the file on every request, so the new password works at once. Update it on every SDK and collector that sends traces.
Then update or delete JAEGER_PASSWORD in /root/jaeger-credentials.txt (sudo nano /root/jaeger-credentials.txt). To give each team or collector its own login, append another user:hash line in the same format.
Security recommendations
- Keep port 443 open only to the networks that need the web UI and to the applications or collectors that send traces, and keep port 22 open only to your admin network. Never open 16686, 16685, 4317, 4318 or 13133.
- Use your own DNS name and a certificate from a certificate authority that your senders trust (Step 9).
- Keep the password in a secrets store and change it when people leave (Step 12). Traces can carry URLs, user identifiers and query text, so treat them as sensitive data and scrub attributes in your SDK or collector where needed.
- The instance applies Ubuntu security updates automatically. To update Jaeger itself, read the release notes, back up
/var/lib/jaeger/data, and replace/usr/local/bin/jaegerwith a newer build. Check its checksum against the release'ssha256sum.txt, then restart the service.
Support and licensing
Jaeger is open-source software licensed under the Apache License 2.0. This image ships Jaeger 2.22.0 compiled from the unmodified upstream source (github.com/jaegertracing/jaeger at v2.22.0, with the jaeger-ui v2.22.0 web assets) and one dependency update (golang.org/x/net 0.58.0 to 0.60.0). The licence, the NOTICE file and the full build record are in /usr/share/doc/jaeger, and SecureAnyCloud provides the corresponding source on request. nginx is licensed under the 2-clause BSD license.
SecureAnyCloud provides 24/7 support for this image by email at sales@secureanycloud.com and through the SecureAnyCloud website. Support covers launching, instrumenting applications and collectors, reading traces, storage and retention, TLS with your own domain, upgrades and troubleshooting.
Jaeger is a trademark of The Linux Foundation. OpenTelemetry is a trademark of The Linux Foundation. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.
Deploy on AWS
Launch Jaeger 2.22.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com