Start by testing an explicit proxy request to pinpoint the problem, then adjust the system proxy or DNS if needed. A client showing “Connected” usually means only that the core or traffic interception has started. It does not confirm that browser requests pass through the core or that the selected node can reach the site. Check in this order: listening port → explicit request → app proxy settings → DNS → rules and nodes → TUN. Change one thing at a time.

1. Make sure the core is listening—not just the GUI

Check the process and port first. These commands only read status and don’t change your configuration. ss may need administrator privileges to show process details; without them, you can still check the listening address and port.

pgrep -af 'mihomo|clash'
sudo ss -lntp 'sport = :7890'
  • No listener found: Check whether the core failed to start, the config failed to parse, or the actual port differs from 7890.
  • Listening on 127.0.0.1:7890: Local applications can connect, but other devices on your LAN can’t use this endpoint directly.
  • Listening on 0.0.0.0:7890: The endpoint is available on local IPv4 interfaces. Also check LAN access permissions and your firewall.
  • Port already in use by another process: Identify the process first, then stop the conflicting instance. Don’t terminate every proxy process at once.

mixed-port accepts both HTTP and SOCKS requests. If your config uses port or socks-port instead, match each protocol to its corresponding port. external-controller is the management API, not a browser proxy port.

# Check the config in this directory only; this won't start another persistent instance
mihomo -t -d "$HOME/.config/mihomo"

A successful test only confirms that the current binary can parse the config; it doesn’t prove the service is using it. For a custom systemd user service, use the following command to check its startup arguments. For a system-wide service, use sudo systemctl and omit --user.

systemctl --user cat mihomo.service
systemctl --user status mihomo.service --no-pager
journalctl --user -u mihomo.service -b -n 80 --no-pager

If the service isn’t found, check the core logs in your GUI client instead. Don’t start another service on the same port just for troubleshooting. Look for address already in use, config parsing failures, permission errors, and Provider loading errors.

2. Use curl with an explicit proxy to separate app and core issues

Test the HTTP proxy endpoint first. --noproxy "" clears the bypass list for this request, preventing NO_PROXY in your environment from accidentally sending it direct. The example uses the same HTTPS URL to make results easier to compare. Repeat the test with the site you can’t reach.

curl --noproxy "" -x http://127.0.0.1:7890 \
  --connect-timeout 5 --max-time 20 \
  -v -o /dev/null https://example.com/
ResultWhat it meansNext step
Connection refusedLocal TCP connection refusedCheck the listening port and address
407Proxy requires authenticationCheck proxy authentication settings and app credentials
CONNECT established, then timed outThe proxy endpoint responded, but the connection didn’t completeCheck rules, nodes, and destination connection logs
certificate verify failedTLS verification failedCheck the system clock, certificate store, and any intermediate proxy
Received an HTTP response from the destinationThis request path can complete a round tripCheck browser and system proxy settings

An HTTP 403 or 429 means the destination or an intermediate service responded; it doesn’t mean the port is down. A successful request only confirms that this destination and rule path worked. It doesn’t prove that every site, UDP, or IPv6 works.

Compare with a request that doesn’t use an explicit proxy

curl --proxy "" --noproxy "*" \
  --connect-timeout 5 --max-time 20 \
  -I https://example.com/

This command disables curl’s explicit proxy, but it won’t bypass TUN, policy routing, or transparent forwarding on your system. To establish a true direct-connection baseline, temporarily turn off TUN in an environment you can restore, then run the test again. If the explicit proxy works but the browser doesn’t, start with the next section; don’t change DNS yet.

3. Check system proxy, browser settings, and terminal environment variables

The Linux desktop proxy, terminal environment variables, and browser settings are separate ways to route traffic. Enabling one doesn’t automatically configure the others. On GNOME, go to Settings → Network → Network Proxy, select Manual, and set the HTTP and HTTPS proxy host to 127.0.0.1 and the port to 7890. Menu names may vary slightly by distribution.

The HTTPS field means “which proxy should handle HTTPS requests?”—it doesn’t mean the proxy protocol itself must be HTTPS. With a local mixed port, https_proxy in the terminal is usually set to http://127.0.0.1:7890.

# Show proxy variables in the current shell; output may contain credentials, so don't share it publicly
env | grep -iE '^(http|https|all|no)_proxy='

# Set a proxy for this curl request only
http_proxy=http://127.0.0.1:7890 \
https_proxy=http://127.0.0.1:7890 \
curl --noproxy "" --max-time 20 -I https://example.com/

Check bypass lists and duplicate interception

  • NO_PROXY=* makes programs that honor this variable bypass the proxy. Clear it for testing, then add local and intranet exceptions back one at a time.
  • In Firefox, go to Settings → General → Network Settings → Settings to check whether it uses the system proxy or a manual proxy. Extensions may override this setting.
  • Existing shells, running IDEs, and background services don’t automatically inherit variables from a new terminal. After changing them, verify the environment of the affected process.
  • Inside a container, 127.0.0.1 points to the container itself, not the host. A working port on the host doesn’t prove that the container can connect to it.

If you suspect stale variables are interfering, run a clean test with the command below. It affects only this child process and won’t remove persistent settings from your shell config files.

env -u http_proxy -u https_proxy -u all_proxy -u no_proxy \
  -u HTTP_PROXY -u HTTPS_PROXY -u ALL_PROXY -u NO_PROXY \
  curl --noproxy "" -x http://127.0.0.1:7890 \
  --max-time 20 -I https://example.com/

4. Distinguish system DNS, core DNS, and fake-ip

A domain lookup failure isn’t always caused by system DNS. HTTP CONNECT can pass the destination hostname to the proxy; SOCKS lets you choose which side resolves it. Compare these two commands: the first resolves the name locally, while the second sends it to the SOCKS proxy for resolution.

curl --noproxy "" --socks5 127.0.0.1:7890 \
  --max-time 20 -I https://example.com/

curl --noproxy "" --socks5-hostname 127.0.0.1:7890 \
  --max-time 20 -I https://example.com/

If local resolution fails but proxy-side resolution works, check the system resolver first. This is a clue, not a definitive diagnosis: the two connections may resolve to different addresses and match different rules. Next, inspect the resolver configuration the system is actually using.

getent ahosts example.com
ls -l /etc/resolv.conf
cat /etc/resolv.conf

# Only for systems running systemd-resolved
resolvectl status
resolvectl query example.com

If /etc/resolv.conf points to a file managed by systemd-resolved or NetworkManager, overwriting it may stop working after reconnecting. Change DNS through the relevant network manager instead of letting DHCP update it while also editing it by hand.

Check mihomo's DNS listener and fake-ip path

Assuming the active config includes dns.listen: 127.0.0.1:1053, query the core DNS directly from a terminal with dig installed. If the listener uses a different port, update the command accordingly.

dig @127.0.0.1 -p 1053 example.com A
dig @127.0.0.1 -p 1053 example.com AAAA

With enhanced-mode: fake-ip, a domain may resolve to a synthetic address in the configured range, commonly 198.18.0.1/16. This isn’t the destination’s public IP. The connection must go back through the proxy core, which holds the domain mapping. If an app receives a fake IP but bypasses traffic interception and connects directly, it may time out repeatedly.

Upstream DNS may itself need name resolution or a proxy connection. Check the purpose and current settings of default-nameserver, nameserver, and proxy-server-nameserver. Make sure resolving a node’s hostname doesn’t depend on a proxy connection that hasn’t been established yet. See the configuration reference for field details. Don’t change all upstream servers at once and then guess which change helped.

5. Check rule matches and node availability—not just latency

Find the test domain in the client’s connection log and check whether the final outbound is DIRECT, a proxy node, or a reject action. Rules are evaluated from top to bottom, with MATCH as the fallback. If an earlier domain or IP rule matches, changing the fallback group won’t affect that request.

  1. Note the test time and destination domain, then run the explicit-proxy curl request again.
  2. Find the domain in the connection log and check which rule and policy group matched.
  3. Check the policy group’s current selection, especially the actual node selected by nested groups.
  4. Temporarily select another known-good node. Keep the destination, request method, and mode unchanged, then compare.

A successful latency test only means the configured test URL was reachable at that time; it doesn’t mean every site is reachable. A node may fail only for certain destinations, protocols, or address families. A successful subscription update only confirms that the config was downloaded—it doesn’t verify node authentication, quota, or server port.

Temporarily increase the log level, then restore it

If needed, temporarily set log-level in the active config to debug, reproduce the issue once, then restore it to info. In a GUI client, use its config override or log settings so an update doesn’t overwrite the generated runtime config. Remove subscription URLs, credentials, and private hostnames before sharing logs.

# For a custom user service: test the config first, then restart only if the test succeeds
mihomo -t -d "$HOME/.config/mihomo" && \
  systemctl --user restart mihomo.service

journalctl --user -u mihomo.service --since "5 minutes ago" --no-pager

If you see a certificate error, first run timedatectl status to check the system clock and sync status. Then check the certificate store and node hostname. Don’t disable certificate verification as a routine fix; it can hide an incorrect address or an issue in the connection path.

6. Explicit proxy works but TUN fails: check permissions, routes, and IPv6

If curl -x works but the browser fails with TUN enabled, you’ve already confirmed that the port and node are usable. Focus on transparent interception. TUN relies on a virtual network interface, the required permissions, and routing. tun.enable: true in the config doesn’t guarantee these conditions are met.

ls -l /dev/net/tun
ip -br link
ip rule show
ip route show table all
ip -6 route show table all
  • /dev/net/tun is missing: Check that the system supports and has enabled TUN. Containers and restricted virtualization environments also need authorization from the host.
  • Logs show interface creation or route changes failing: Use the service mode provided by your client, or check permissions for a custom service. Don’t loosen executable permissions indiscriminately.
  • Running another VPN at the same time: Compare both tools’ policy routes, default routes, and DNS interception. Test with only one traffic interception path enabled at a time.
  • IPv4 works but IPv6 fails: Check the IPv6 default route, DNS AAAA responses, and the active core’s IPv6 handling settings.
# With TUN disabled, compare the host's native IPv4 / IPv6 connectivity
curl --proxy "" --noproxy "*" -4 --max-time 15 -I https://example.com/
curl --proxy "" --noproxy "*" -6 --max-time 15 -I https://example.com/

Here, -4 and -6 check direct connectivity. Using the same options with a local HTTP proxy won’t reliably control which address family the remote node uses to reach the destination. If your network has no IPv6 connectivity, a failed IPv6 test doesn’t mean the node is broken.

7. Restore service with minimal changes and keep a record

When you’re done troubleshooting, retest the same destination through the terminal, browser, and TUN. Don’t rely on the tray icon or a single speed test to confirm that it’s fixed. Keep these notes to make future recurrences easier to compare.

  1. Running instance: Record the core version, active config path, listening address, and mixed-port. Check the version with mihomo -v.
  2. App proxy: Record whether the browser uses the system or a manual proxy, and whether proxy variables are set in the terminal.
  3. Resolution path: Record system DNS, the core DNS listening port, whether fake-ip is enabled, and whether Secure DNS is enabled in the browser.
  4. Outbound result: Record the rule matched by the test domain, the actual node, and the time of the error. Don’t record subscription keys.
  5. Restore settings: Return debug logging to its normal level, and undo temporary global modes and any test changes you no longer need.

The most useful way to narrow it down: if the explicit proxy fails, check the core and outbound connection; if the explicit proxy works but the browser fails, check the app’s proxy settings; if only TUN fails, check transparent interception; if only local resolution fails, follow the system DNS path. To rebuild a basic setup, follow the quick start to get a minimal working connection, then restore rules, Providers, and TUN one at a time.