diff --git a/basicswap/bin/prepare.py b/basicswap/bin/prepare.py
index 662f4f0..c6715f9 100755
--- a/basicswap/bin/prepare.py
+++ b/basicswap/bin/prepare.py
@@ -283,18 +283,23 @@ TOR_PROXY_HOST = os.getenv("TOR_PROXY_HOST", "127.0.0.1")
 TOR_PROXY_PORT = int(os.getenv("TOR_PROXY_PORT", 9050))
 TOR_CONTROL_PORT = int(os.getenv("TOR_CONTROL_PORT", 9051))
 TOR_DNS_PORT = int(os.getenv("TOR_DNS_PORT", 5353))
-TOR_CONTROL_LISTEN_INTERFACE = os.getenv(
-    "TOR_CONTROL_LISTEN_INTERFACE", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
-)
-TORRC_PROXY_HOST = os.getenv(
-    "TORRC_PROXY_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
-)
-TORRC_CONTROL_HOST = os.getenv(
-    "TORRC_CONTROL_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
-)
-TORRC_DNS_HOST = os.getenv(
-    "TORRC_DNS_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
-)
+
+
+def setTorrcVars():
+    global TOR_CONTROL_LISTEN_INTERFACE, TORRC_PROXY_HOST, TORRC_CONTROL_HOST, TORRC_DNS_HOST
+    TOR_CONTROL_LISTEN_INTERFACE = os.getenv(
+        "TOR_CONTROL_LISTEN_INTERFACE", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
+    )
+    TORRC_PROXY_HOST = os.getenv(
+        "TORRC_PROXY_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
+    )
+    TORRC_CONTROL_HOST = os.getenv(
+        "TORRC_CONTROL_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
+    )
+    TORRC_DNS_HOST = os.getenv(
+        "TORRC_DNS_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
+    )
+
 
 TEST_TOR_PROXY = toBool(
     os.getenv("TEST_TOR_PROXY", "true")
@@ -2073,7 +2078,69 @@ def load_config(config_path):
     if not os.path.exists(config_path):
         exitWithError("{} does not exist".format(config_path))
     with open(config_path) as fs:
-        return json.load(fs)
+        settings = json.load(fs)
+
+    BSX_ALLOW_ENV_OVERRIDE = toBool(os.getenv("BSX_ALLOW_ENV_OVERRIDE", "false"))
+
+    saved_env_var_settings = [
+        ("setup_docker_mode", "BSX_DOCKER_MODE"),
+        ("setup_local_tor", "BSX_LOCAL_TOR"),
+        ("setup_tor_control_listen_interface", "TOR_CONTROL_LISTEN_INTERFACE"),
+        ("setup_torrc_proxy_host", "TORRC_PROXY_HOST"),
+        ("setup_torrc_control_host", "TORRC_CONTROL_HOST"),
+        ("setup_torrc_dns_host", "TORRC_DNS_HOST"),
+        ("tor_proxy_host", "TOR_PROXY_HOST"),
+        ("tor_proxy_port", "TOR_PROXY_PORT"),
+        ("tor_control_port", "TOR_CONTROL_PORT"),
+    ]
+    for setting in saved_env_var_settings:
+        config_name, env_name = setting
+        env_value = globals()[env_name]
+        saved_config_value = settings.get(config_name, env_value)
+        if saved_config_value != env_value:
+            if os.getenv(env_name):
+                # If the env var was manually set override the saved config if allowed else fail.
+                if BSX_ALLOW_ENV_OVERRIDE:
+                    logger.warning(
+                        f"Env var {env_name} differs from saved config '{config_name}', overriding."
+                    )
+                else:
+                    print(
+                        f"Env var {env_name} differs from saved config '{config_name}', set 'BSX_ALLOW_ENV_OVERRIDE' to override.",
+                        file=sys.stderr,
+                    )
+                    sys.exit(1)
+            else:
+                logger.info(f"Setting {env_name} from saved config '{config_name}'.")
+                globals()[env_name] = saved_config_value
+                # Recalculate env vars that depend on the changed var
+                if env_name == "BSX_LOCAL_TOR":
+                    setTorrcVars()
+    return settings
+
+
+def save_config(config_path, settings, add_options: bool = True) -> None:
+
+    if add_options is True:
+        if os.getenv("BSX_DOCKER_MODE") or "docker_mode" not in settings:
+            settings["setup_docker_mode"] = BSX_DOCKER_MODE
+        if os.getenv("BSX_LOCAL_TOR") or "local_tor" not in settings:
+            settings["setup_local_tor"] = BSX_LOCAL_TOR
+
+        # Add to settings only if manually set
+        if os.getenv("TOR_CONTROL_LISTEN_INTERFACE"):
+            settings["setup_tor_control_listen_interface"] = (
+                TOR_CONTROL_LISTEN_INTERFACE
+            )
+        if os.getenv("TORRC_PROXY_HOST"):
+            settings["setup_torrc_proxy_host"] = TORRC_PROXY_HOST
+        if os.getenv("TORRC_CONTROL_HOST"):
+            settings["setup_torrc_control_host"] = TORRC_CONTROL_HOST
+        if os.getenv("TORRC_DNS_HOST"):
+            settings["setup_torrc_dns_host"] = TORRC_DNS_HOST
+
+    with open(config_path, "w") as fp:
+        json.dump(settings, fp, indent=4)
 
 
 def signal_handler(sig, frame):
@@ -2143,6 +2210,7 @@ def ensure_coin_valid(coin: str, test_disabled: bool = True) -> None:
 
 def main():
     global use_tor_proxy, with_coins_changed
+    setTorrcVars()
     data_dir = None
     bin_dir = None
     port_offset = None
@@ -2735,9 +2803,7 @@ def main():
                 settings, coin, tor_control_password, enable=True, extra_opts=extra_opts
             )
 
-        with open(config_path, "w") as fp:
-            json.dump(settings, fp, indent=4)
-
+        save_config(config_path, settings)
         logger.info("Done.")
         return 0
 
@@ -2754,9 +2820,7 @@ def main():
                 extra_opts=extra_opts,
             )
 
-        with open(config_path, "w") as fp:
-            json.dump(settings, fp, indent=4)
-
+        save_config(config_path, settings)
         logger.info("Done.")
         return 0
 
@@ -2781,9 +2845,7 @@ def main():
         if "manage_wallet_daemon" in coin_settings:
             coin_settings["manage_wallet_daemon"] = False
 
-        with open(config_path, "w") as fp:
-            json.dump(settings, fp, indent=4)
-
+        save_config(config_path, settings)
         logger.info("Done.")
         return 0
 
@@ -2805,8 +2867,7 @@ def main():
                 coin_settings["manage_daemon"] = True
                 if "manage_wallet_daemon" in coin_settings:
                     coin_settings["manage_wallet_daemon"] = True
-                with open(config_path, "w") as fp:
-                    json.dump(settings, fp, indent=4)
+                save_config(config_path, settings)
                 logger.info("Done.")
                 return 0
             exitWithError("{} is already in the settings file".format(add_coin))
@@ -2821,7 +2882,6 @@ def main():
             test_particl_encryption(data_dir, settings, chain, use_tor_proxy)
 
         settings["chainclients"][add_coin] = chainclients[add_coin]
-        settings["use_tor_proxy"] = use_tor_proxy
 
         if not no_cores:
             prepareCore(add_coin, known_coins[add_coin], settings, data_dir, extra_opts)
@@ -2843,8 +2903,7 @@ def main():
                     use_tor_proxy,
                 )
 
-            with open(config_path, "w") as fp:
-                json.dump(settings, fp, indent=4)
+            save_config(config_path, settings)
 
         logger.info(f"Done. Coin {add_coin} successfully added.")
         return 0
@@ -2863,8 +2922,7 @@ def main():
                 if c not in settings["chainclients"]:
                     settings["chainclients"][c] = chainclients[c]
         elif upgrade_cores:
-            with open(config_path) as fs:
-                settings = json.load(fs)
+            settings = load_config(config_path)
 
             with_coins_start = with_coins
             if not with_coins_changed:
@@ -2913,8 +2971,8 @@ def main():
             # Run second loop to update, so all versions are logged together.
             # Backup settings
             old_config_path = config_path[:-5] + "_" + str(int(time.time())) + ".json"
-            with open(old_config_path, "w") as fp:
-                json.dump(settings, fp, indent=4)
+            save_config(old_config_path, settings, add_options=False)
+
             for c in with_coins:
                 prepareCore(c, known_coins[c], settings, data_dir, extra_opts)
                 current_coin_settings = chainclients[c]
@@ -2927,8 +2985,7 @@ def main():
                     settings["chainclients"][c][
                         "core_version_group"
                     ] = current_version_group
-                with open(config_path, "w") as fp:
-                    json.dump(settings, fp, indent=4)
+                save_config(config_path, settings)
 
             logger.info("Done.")
             return 0
@@ -2978,8 +3035,7 @@ def main():
     for c in with_coins:
         prepareDataDir(c, settings, chain, particl_wallet_mnemonic, extra_opts)
 
-    with open(config_path, "w") as fp:
-        json.dump(settings, fp, indent=4)
+    save_config(config_path, settings)
 
     if particl_wallet_mnemonic == "none":
         logger.info("Done.")
diff --git a/tests/basicswap/extended/test_network.py b/tests/basicswap/extended/test_network.py
index 872ed2d..15df05d 100644
--- a/tests/basicswap/extended/test_network.py
+++ b/tests/basicswap/extended/test_network.py
@@ -197,7 +197,7 @@ class Test(unittest.TestCase):
         logger.handlers = []
         logger.setLevel(logging.INFO)  # DEBUG shows many messages from requests.post
         formatter = logging.Formatter("%(asctime)s %(levelname)s : %(message)s")
-        stream_stdout = logging.StreamHandler()
+        stream_stdout = logging.StreamHandler(sys.stdout)
         stream_stdout.setFormatter(formatter)
         logger.addHandler(stream_stdout)
 
diff --git a/tests/basicswap/extended/test_prepare.py b/tests/basicswap/extended/test_prepare.py
index d65990c..7cb1fd2 100644
--- a/tests/basicswap/extended/test_prepare.py
+++ b/tests/basicswap/extended/test_prepare.py
@@ -6,30 +6,34 @@
 # Distributed under the MIT software license, see the accompanying
 # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 
-import os
-import sys
+import importlib
 import json
-import shutil
 import logging
-import unittest
-import threading
 import multiprocessing
+import os
+import shutil
+import sys
+import threading
+import unittest
 
 from io import StringIO
 from unittest.mock import patch
 
 import basicswap.config as cfg
 from tests.basicswap.util import (
+    make_boolean,
     read_json_api,
     waitForServer,
 )
 
+TEST_DELETE_DIRS = make_boolean(os.getenv("TEST_DELETE_DIRS", True))
 bin_path = os.path.expanduser(os.getenv("TEST_BIN_PATH", ""))
 test_base_path = os.path.expanduser(os.getenv("TEST_PREPARE_PATH", "~/test_basicswap"))
 test_path_plain = os.path.join(test_base_path, "plain")
 test_path_encrypted = os.path.join(test_base_path, "encrypted")
 test_path_encrypt = os.path.join(test_base_path, "encrypt")
 
+
 delay_event = threading.Event()
 logger = logging.getLogger()
 logger.level = logging.DEBUG
@@ -59,14 +63,28 @@ def start_run(args, env_pairs=[]):
 
 
 class Test(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        super(Test, cls).setUpClass()
+        # Reset env vars
+        reset_vars = ["BSX_ALLOW_ENV_OVERRIDE", "BSX_DOCKER_MODE", "BSX_LOCAL_TOR"]
+        for var_name in reset_vars:
+            if var_name in os.environ:
+                del os.environ[var_name]
+
     @classmethod
     def tearDownClass(self):
         try:
-            for test_dir in (test_path_plain, test_path_encrypted, test_path_encrypt):
-                if os.path.exists(test_dir):
-                    shutil.rmtree(test_dir)
+            if TEST_DELETE_DIRS:
+                for test_dir in (
+                    test_path_plain,
+                    test_path_encrypted,
+                    test_path_encrypt,
+                ):
+                    if os.path.exists(test_dir):
+                        shutil.rmtree(test_dir)
         except Exception as ex:
-            logger.warning("tearDownClass %s", str(ex))
+            logger.warning(f"tearDownClass {ex}")
         super(Test, self).tearDownClass()
 
     def test_plain(self):
@@ -146,6 +164,7 @@ class Test(unittest.TestCase):
                 self.assertTrue(
                     settings["chainclients"]["bitcoin"]["connection_type"] == "rpc"
                 )
+                assert settings.get("use_tor", False) is False
 
             logging.info("notorproxy")
             testargs = [
@@ -165,8 +184,89 @@ class Test(unittest.TestCase):
                 "--usetorproxy and --notorproxy together" in fake_stderr.getvalue()
             )
 
+            logger.info("Test persistent setup config.")
+
+            with open(config_path) as fs:
+                settings = json.load(fs)
+            assert settings.get("use_tor", False) is False
+            assert settings["setup_docker_mode"] is False
+            assert settings["setup_local_tor"] is False
+            assert "setup_tor_control_listen_interface" not in settings
+            assert "setup_torrc_dns_host" not in settings
+
+            os.environ["BSX_LOCAL_TOR"] = "true"
+            # Reimport to reset globals and set env
+            importlib.reload(prepareSystem)
+
+            testargs = [
+                "basicswap-prepare",
+                "-datadir=" + test_path_plain,
+                "--enabletor",
+            ]
+            with patch("sys.stderr", new=StringIO()) as fake_stderr:
+                with patch.object(sys, "argv", testargs):
+                    with self.assertRaises(SystemExit) as cm:
+                        prepareSystem.main()
+
+            self.assertEqual(cm.exception.code, 1)
+            self.assertTrue(
+                "Env var BSX_LOCAL_TOR differs from saved config"
+                in fake_stderr.getvalue()
+            )
+
+            os.environ["BSX_ALLOW_ENV_OVERRIDE"] = "true"
+            os.environ["TOR_CONTROL_LISTEN_INTERFACE"] = "127.1.1.1"
+            importlib.reload(prepareSystem)
+
+            with patch.object(sys, "argv", testargs):
+                prepareSystem.main()
+
+            with open(config_path) as fs:
+                settings = json.load(fs)
+            assert settings.get("use_tor", False) is True
+            assert settings["setup_docker_mode"] is False
+            assert settings["setup_local_tor"] is True
+            assert settings["setup_tor_control_listen_interface"] == "127.1.1.1"
+            assert "setup_torrc_dns_host" not in settings
+
+            particl_config_path = os.path.join(
+                test_path_plain, "particl", "particl.conf"
+            )
+            with open(particl_config_path) as fs:
+                particl_conf = fs.read()
+                assert "bind=127.1.1.1:" in particl_conf
+
+            testargs = [
+                "basicswap-prepare",
+                "-datadir=" + test_path_plain,
+                "-disablecoin=bitcoin",
+            ]
+            with patch.object(sys, "argv", testargs):
+                prepareSystem.main()
+
+            os.environ["TOR_PROXY_HOST"] = "127.2.2.2"
+            del os.environ["BSX_ALLOW_ENV_OVERRIDE"]
+            importlib.reload(prepareSystem)
+
+            testargs = [
+                "basicswap-prepare",
+                "-datadir=" + test_path_plain,
+                "-addcoin=bitcoin",
+                "-notorproxy",  # Disable TOR connection check
+            ]
+            with patch("sys.stderr", new=StringIO()) as fake_stderr:
+                with patch.object(sys, "argv", testargs):
+                    with self.assertRaises(SystemExit) as cm:
+                        prepareSystem.main()
+
+            self.assertEqual(cm.exception.code, 1)
+            self.assertTrue(
+                "Env var TOR_PROXY_HOST differs from saved config"
+                in fake_stderr.getvalue()
+            )
+
         finally:
-            del prepareSystem
+            del prepareSystem  # Does not cause next import to refresh globals and env (tested v3.13)
 
     def test_encrypted(self):
         if os.path.exists(test_path_encrypted):
diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py
index a85c7db..63b94a1 100644
--- a/tests/basicswap/test_xmr.py
+++ b/tests/basicswap/test_xmr.py
@@ -12,6 +12,7 @@ import os
 import random
 import shutil
 import signal
+import sys
 import threading
 import time
 import traceback
@@ -350,7 +351,7 @@ class BaseTest(unittest.TestCase):
         logger.handlers = []
         logger.setLevel(logging.INFO)  # DEBUG shows many messages from requests.post
         formatter = logging.Formatter("%(asctime)s %(levelname)s : %(message)s")
-        stream_stdout = logging.StreamHandler()
+        stream_stdout = logging.StreamHandler(sys.stdout)
         stream_stdout.setFormatter(formatter)
         logger.addHandler(stream_stdout)
 
diff --git a/tests/basicswap/util.py b/tests/basicswap/util.py
index 81a11c3..3b55a2e 100644
--- a/tests/basicswap/util.py
+++ b/tests/basicswap/util.py
@@ -19,7 +19,11 @@ REQUIRED_SETTINGS = {
 }
 
 
-def make_boolean(s):
+def make_boolean(s) -> bool:
+    if isinstance(s, bool):
+        return s
+    if isinstance(s, int):
+        return False if s == 0 else True
     return s.lower() in ["1", "true"]