the phone-home register
Turn them off
This page is for anyone who has read a row on the register and wants to set its switch. It says where a switch has to be set so that the program actually reads it, which switches do nothing but stop a report, which ones turn something else off too and what that costs, which programs have no switch we could find, and how to tell whether a program has already run on your machine with its reporting on. It is written for Linux; each row says which platforms it was read or measured on. Nothing on this page says you should turn a given call off: some of them keep you safe or up to date, and each row says what you lose.
Where a switch has to be set
Most of the switches on the register are environment variables, written here as NAME=value. A variable is read by the program at start, from the environment it was started with, and then fixed for that process; setting it later, or in a different place, does nothing, and nothing tells you so. The most expensive lesson behind this site is that a switch set in the wrong place looks exactly like a switch set in the right one.
Your terminalsAddexport NAME=value in ~/.bashrc or ~/.zshrc, and in ~/.profile on Linux, which many desktop sessions read at login
- Add
- export NAME=value in ~/.bashrc or ~/.zshrc, and in ~/.profile on Linux, which many desktop sessions read at login
- Then
- Open a new terminal; restart anything that was already running
Cron jobs and ssh host 'command', on Debian or UbuntuAddNAME=value in /etc/environment (no export)
- Add
- NAME=value in /etc/environment (no export)
- Then
- Nothing: each new job, session or login reads it
A systemd serviceAddEnvironment=NAME=value in a drop-in (the recipe below)
- Add
- Environment=NAME=value in a drop-in (the recipe below)
- Then
- Reload systemd, then restart the service
docker runAdd-e NAME=value
- Add
- -e NAME=value
- Then
- Remove the container and run it again: a plain restart keeps the old environment
Docker ComposeAddNAME: "value" under the service's environment:
- Add
- NAME: "value" under the service's environment:
- Then
- docker compose up -d, which recreates the containers whose settings changed
Your own Python codeAddos.environ.setdefault("NAME", "value") above the imports
- Add
- os.environ.setdefault("NAME", "value") above the imports
- Then
- Restart the program; in a notebook, restart the kernel
Your terminals. In a new terminal, printenv NAME should print the value. Anything already running keeps the environment it started with: open terminals, a Jupyter server, your editor, and tmux or screen sessions, which on most Linux systems outlive a logout; restart each one. sudo normally starts commands with a cleaned environment, so pass the switch through it: sudo env NAME=value python3 tool.py.
Cron jobs and ssh commands read neither shell file: Debian's and Ubuntu's stock ~/.bashrc stops at once when the shell is not interactive, and cron reads no shell file at all. The /etc/environment line reaches them, and every new login, through PAM (the login system's authentication modules); services and containers do not read it.
A systemd service. Change only the first two lines, naming the unit in full, with .service:
svc=myapp.service
var=NAME=value
d=/etc/systemd/system/$svc.d
sudo mkdir -p "$d"
printf '[Service]\nEnvironment=%s\n' "$var" \
| sudo tee "$d/zz-callhome-${var%%=*}.conf"
sudo systemctl daemon-reload
systemctl show -p Environment "$svc" \
| grep -q -F "$var" \
&& sudo systemctl restart "$svc"
The restart runs only once systemd has loaded the line; if nothing restarts, check the unit's name. The zz- sorts the file last, so it wins over any other drop-in's Environment= line for that variable; a value from an EnvironmentFile= would still win, which the check under "Prove it took" shows. For a user service, use d=~/.config/systemd/user/$svc.d, drop sudo, and add --user to each systemctl. A systemd unit that runs docker run needs the -e on that command line: an Environment= line on the unit reaches the docker command, not the container.
Docker Compose. If the service already has an environment: list (lines beginning - ), add - NAME=value to it rather than a second environment: key. In a Dockerfile, ENV NAME=value, then rebuild and recreate. To read what a container was created with, by its name from docker ps:
docker inspect myapp -f '{{json .Config.Env}}'
Switches that are files, not variables. A few programs read a file instead, or as well: Ollama's disable_ollama_cloud in server.json, in the .ollama folder under the home folder of the account the server runs as; vLLM's empty do_not_track file in the .config/vllm folder under that home folder; OpenVINO's intel/openvino_telemetry file holding 0; ComfyUI-Manager's network_mode = offline in its config.ini. A file switch does not depend on the shell the program was started from, which is its advantage; its trap is the home folder: a service runs as its own account, and a file in your home folder is not in its.
Prove it took. Wait until the restarted program has logged its first line (straight after a restart you may be reading systemd's own launcher, not your program), then read the environment the working process started with:
pid=$(systemctl show -p MainPID --value myapp.service)
sudo cat /proc/$pid/environ | tr '\0' '\n' | grep '^NAME=' || echo NOT SET
It should print NAME=value; NOT SET means the switch did not reach this process. For a program that sets the switch from inside its own code, that file holds only the environment it started with, so look for the tool's own sign instead: Ollama logs Ollama cloud disabled: true when its switch took; vLLM keeps a copy of what it sent in usage_stats.json while the report is on; ONNX Runtime creates an empty mat-debug-<process number>.log in the process's temporary folder only while telemetry is on. The rows say which sign each product has.
The switches that only turn off reporting
Each line below stops one program's report and, as far as its row shows, nothing else. The comment names the product, the grade of the row behind it, and whether we proved the switch. A line for a program you do not run does nothing. Set them where the program reads them (above), not only here.
environment:
# ONNX Runtime 1.29.0 and newer · measured · proven on 1.30.0 (PyPI wheel, Linux x86-64, launch to 120 s, 3 of 3 runs; steps 3 and 5 not met)
ORT_DISABLE_TELEMETRY: "1"
# vLLM · read, from its own usage page and source · found in code and documented; we have not published a test of it
VLLM_NO_USAGE_STATS: "1"
# Qdrant · read, from its source · not tested by us
QDRANT__TELEMETRY_DISABLED: "true"
# pyannote.audio · read, from its shipped configuration and README · not tested by us
PYANNOTE_METRICS_ENABLED: "0"
# huggingface_hub · read, from its own documentation · not tested by us (stops the telemetry fields, not the Hub calls)
HF_HUB_DISABLE_TELEMETRY: "1"
ONNX Runtime's switch takes 1, true, yes, on or y, in upper or lower case; any other value (0, false, empty) leaves its telemetry on. It is read once, when the library first initialises (in Python, at import), so the variable has to be in place before the program starts, and onnxruntime.disable_telemetry_events(), called from code, left the uploader running in our test. Some programs honour DO_NOT_TRACK=1 as well; a short table below says which.
Switches with side effects
Each of these stops a call; where it turns something else off, the line says what. What it costs is from the maker's own document, named on the row. We have not published a test of any of these: each comes from the maker's document or code at the version its row names, and the row gives its proof state.
- OLLAMA_NO_CLOUD=1 (or disable_ollama_cloud in server.json) stops Ollama's two fetches from ollama.com, and with them its cloud models and its web search. Ollama's FAQ calls this "local only mode". Models you have already pulled keep working.
- HF_HUB_OFFLINE=1 stops every call huggingface_hub makes to the Hugging Face Hub, telemetry included, and so every download through it: a model that is not already in the local cache will not load. Use it once your models are cached.
- GRADIO_ANALYTICS_ENABLED=False stops Gradio's analytics events and, with them, its check for a newer version, so you are no longer warned of one; it also sets HF_HUB_DISABLE_TELEMETRY for that process.
- FLASHINFER_NO_DOWNLOAD=1 stops FlashInfer from fetching a kernel binary it is missing from NVIDIA's artifact server, and a run that needs that kernel then fails instead; the alternative its documentation offers is to install the kernel package (flashinfer-cubin) yourself.
- --offline (or LLAMA_ARG_OFFLINE=1) makes llama.cpp's server use its local cache and refuse -hf downloads. The web UI's model browser asks Hugging Face and llama.app from your browser; --no-webui removes the web UI, and the browser with it. At build time, -DLLAMA_USE_PREBUILT_UI=OFF stops the build from fetching the web UI's files from Hugging Face; unless you place the UI files in tools/ui/dist yourself or build them with -DLLAMA_BUILD_UI=ON (npm), the server is then built without an embedded web UI.
- OMNIVOICE_HF_ENDPOINT_MODE=manual stops VoiceStudio from probing a model mirror and picking a model host for you; you then set the host yourself, and per its own description downloads no longer move to the reachable host when one is blocked.
- NetworkManager's [connectivity] enabled=false stops its periodic request to Ubuntu's connectivity-check address, and with it captive-portal detection: on a hotel or café network, nothing will tell you a login page is in the way.
- Masking snapd's units stops its refresh checks against the snap store, and with them every snap update, and snap commands stop working; snap set system refresh.hold=<date> is the gentler switch its row also names.
- Disabling fwupd-refresh.timer stops fwupd, the firmware updater, downloading firmware metadata from the LVFS (the Linux Vendor Firmware Service); it also stops you being told of firmware updates, which you may want.
- pro config set apt_news=false turns off Ubuntu Pro's APT news, and the code at 37.2ubuntu0.1 skips the fetch while it is set (read; not tested by us).
- ENABLED=0 in /etc/default/motd-news, with the motd-news timer disabled, stops the login-message news fetch and its request, whose user agent names the distribution, the kernel, the processor model and a cloud identifier.
- Disabling pollinate.service stops its request at boot to Ubuntu's entropy service (a source of randomness for a machine that has gathered little of its own yet); its README says Pollinate "seeds (and periodically re-seeds)" the random number generator in cloud virtual machines, and a fresh virtual machine then relies on its own randomness at first boot.
- Masking Ubuntu Insights' two timers (ubuntu-insights-collect.timer and ubuntu-insights-upload.timer, user units) stops its weekly report. With consent off, it still sends an opt-out notice holding only that fact, as its README says ("If consent is not given for a source, an opt-out notification will be sent regardless of the locally cached insights report's contents."); its row carries the program's own upload records from a machine of ours.
- network_mode = offline in ComfyUI-Manager's config.ini stops the Manager's fetches of its channel and node lists from GitHub, and so its catalogue and its updates; ComfyUI itself keeps working.
- PIP_DISABLE_PIP_VERSION_CHECK=1 stops pip's own check for a newer pip, and you stop being told of one. It does not touch the user agent pip and uv send with every index request, for which neither has a switch; see below.
- HF_HUB_DISABLE_UPDATE_CHECK=1 stops the hf command's check for a newer huggingface_hub release, and you are no longer told that one exists; it also stops the hint the command prints about its hf-cli skill.
DO_NOT_TRACK, a general-purpose variable some programs honour, in a table of its own because it is not a switch for most of the register:
huggingface_hubReads DO_NOT_TRACK=1 ?Yes, for the telemetry it sends itself and the fields in its user agent: its documentation says it is equivalent to HF_HUB_DISABLE_TELEMETRY
- Reads DO_NOT_TRACK=1 ?
- Yes, for the telemetry it sends itself and the fields in its user agent: its documentation says it is equivalent to HF_HUB_DISABLE_TELEMETRY
- How we know
- read, its documentation and code
vLLMReads DO_NOT_TRACK=1 ?Yes: one of its three documented spellings
- Reads DO_NOT_TRACK=1 ?
- Yes: one of its three documented spellings
- How we know
- read, its documentation and source
ONNX RuntimeReads DO_NOT_TRACK=1 ?No: the name appears nowhere in its source or in its 1.30.0 Linux libraries
- Reads DO_NOT_TRACK=1 ?
- No: the name appears nowhere in its source or in its 1.30.0 Linux libraries
- How we know
- read: its source and its 1.30.0 Linux libraries searched, on ONNX Runtime's telemetry on Linux, measured
OllamaReads DO_NOT_TRACK=1 ?not read
- Reads DO_NOT_TRACK=1 ?
- not read
- How we know
- no read of ours covers this yet
pyannote.audioReads DO_NOT_TRACK=1 ?not read
- Reads DO_NOT_TRACK=1 ?
- not read
- How we know
- no read of ours covers this yet
GradioReads DO_NOT_TRACK=1 ?not read
- Reads DO_NOT_TRACK=1 ?
- not read
- How we know
- no read of ours covers this yet
A DO_NOT_TRACK=1 set everywhere is not a plan: set each program's own switch.
Programs with no switch we could find
A few programs make a call by default and offer no documented way to stop it. The register says so plainly, "none found", with the version read, rather than guessing. At first release those are: OpenWhispr's update check (its deb, rpm and tar.gz builds carry no updater at all, and the builds that have an updater offer no switch for the check at v1.10.2: its update setting stops downloads, not the check), its session check at each launch, its avatar fetches and the embedding model its assistant downloads on first use; VoiceStudio's update check; LM Studio's update check (its privacy policy says what it carries and why, and names no switch); and the user agent pip and uv send with every request to a package index, which names the installer, the Python version, the operating system and its release and the processor, among other fields each row lists. For these, the only control is your network.
What a block on your network does, and does not. Blocking a maker's host name in a DNS filter such as Pi-hole or AdGuard Home, or at a firewall, keeps the call from leaving through that network. It does not stop the program from collecting: ONNX Runtime, cut off in our test, still created its device ID and its queue and kept retrying, and a block covers only the networks behind it, not a laptop that leaves home. Some programs reach a fixed address with no name lookup at all, and a name block does not see those. So a blocked name is a belt, not a switch: where a switch exists, set it even if you block. The file telemetry-hosts.txt lists the host names on the register whose every row is a telemetry collector, an analytics service or an error tracker, one per line, each with a note on what else blocking it affects, for anyone who wants the belt too.
Has it already run on your machine?
Some calls, and some programs' first start, leave a file behind. Each check below reads the disk and starts nothing; run it with sudo so that find can open other accounts' home folders and Docker's storage, where a service's copy often lives. Each walks every mounted disk, so on a machine with a large media library it can take a while; to skip a mount, add -path /mnt/media -prune -o after -path /proc -prune -o, with your mount's path. Paths are relative to the home folder of the account that ran the program.
ONNX Runtime 1.29.0 and newerWhat a run leaves behind.cache/Microsoft/DeveloperTools/.onnxruntime/onnxruntime.db, with deviceid beside it; .ses in the temporary folder; an empty mat-debug-<process number>.log in the temporary folder
- What a run leaves behind
- .cache/Microsoft/DeveloperTools/.onnxruntime/onnxruntime.db, with deviceid beside it; .ses in the temporary folder; an empty mat-debug-<process number>.log in the temporary folder
- What it tells you
- The .db file is a queue of events: something running as that account ran ONNX Runtime 1.29 or newer with telemetry on. A folder holding only deviceid may come from another Microsoft AI developer tool that shares the file.
vLLMWhat a run leaves behind.config/vllm/usage_stats.json
- What a run leaves behind
- .config/vllm/usage_stats.json
- What it tells you
- A copy of what vLLM sent, field for field, from a run with the report on. The empty do_not_track file beside it, where present, is the file form of the switch, not a trace of a call.
OllamaWhat a run leaves behind.ollama/cache/model-recommendations.json (under the server account's home)
- What a run leaves behind
- .ollama/cache/model-recommendations.json (under the server account's home)
- What it tells you
- The last list it fetched.
Ubuntu InsightsWhat a run leaves behind.cache/ubuntu-insights/<source>/uploaded/, holding dated report files
- What a run leaves behind
- .cache/ubuntu-insights/<source>/uploaded/, holding dated report files
- What it tells you
- Each file is a report the uploader sent as that account.
Any tool using huggingface_hubWhat a run leaves behind.cache/huggingface/token
- What a run leaves behind
- .cache/huggingface/token
- What it tells you
- Not a trace of a call, but a condition for one: while this file exists, a tool that checks who you are sends the token to Hugging Face's own account service, which issued it (VoiceStudio checks each token it finds and keeps the answer for 300 seconds, per its row; how often it asks again was not read).
OpenVINOWhat a run leaves behindintel/openvino_telemetry
- What a run leaves behind
- intel/openvino_telemetry
- What it tells you
- The consent file. 0 means off; with no file, it reports: its documented default. It records a choice, not a call.
For the ONNX Runtime queue, the check that found ours:
sudo find / -path /proc -prune -o \
-path '*/.onnxruntime/onnxruntime.db' \
-print 2>/dev/null
Change the -path pattern for the other rows ('*/.config/vllm/usage_stats.json', '*/ubuntu-insights/*/uploaded/*'). An empty result does not clear the machine: a service whose home folder is read-only to it (systemd's ProtectHome=) leaves nothing there whether or not its reporting ran, and neither does a container since removed. A missing file is not a quiet program.
For anyone setting a switch
Four things we learned the hard way, each with its source where the source is a document.
- Where a switch is set decides whether it works. A switch set in the shell you type in does not reach a server that was started as an app or a service; a systemd unit starts with its manager's environment, not your shell's; and a user-level environment.d file reaches the user manager only, not terminals already open. The table at the top of this page is the whole answer.
- A systemd user service does not enforce IPAddressDeny=: on systemd 259 its journal says so in systemd's own words, "configures an IP firewall, but not running as root". A network namespace does the job instead: bwrap --unshare-net (bubblewrap) for a command, or PrivateNetwork=yes in a system unit.
- IPAddressAllow=localhost still lets a service resolve any name, because the system resolver listens on loopback, and systemd's IP filtering covers IP sockets only (its systemd.resource-control manual: "network traffic filtering for IP packets sent and received over AF_INET and AF_INET6 sockets"): a Unix socket to the resolver or to D-Bus is not one. Allowing loopback is not the same as allowing nothing.
- DO_NOT_TRACK is honoured by some programs and not others. The table above says which, for the programs on the register.