diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorModule.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorModule.kt
index 2e3c5c404dd4c0e538867952ef785b5b0083e6b8..ae750a7966379ed6790f7fc5a314fc89888282cd 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorModule.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorModule.kt
@@ -78,6 +78,10 @@ internal class AndroidTorModule {
                     throw UnsupportedOperationException()
                 }
 
+                override fun onSettingsChanged() {
+                    throw UnsupportedOperationException()
+                }
+
                 override fun getHiddenServiceAddress(): String {
                     throw UnsupportedOperationException()
                 }
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/AbstractTorPlugin.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/AbstractTorPlugin.java
index 8f2d8d3fbe424103a278af26adb4eb36efaf3d0f..f9bd904543619e9c5e4d6ddc3f15e9f73c5092bf 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/AbstractTorPlugin.java
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/AbstractTorPlugin.java
@@ -54,14 +54,24 @@ import kotlinx.coroutines.flow.StateFlow;
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
 import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+import static org.briarproject.mailbox.core.tor.TorConstants.BRIDGE_AUTO;
+import static org.briarproject.mailbox.core.tor.TorConstants.BRIDGE_USE;
+import static org.briarproject.mailbox.core.tor.TorConstants.BRIDGE_USE_MEEK;
+import static org.briarproject.mailbox.core.tor.TorConstants.BRIDGE_USE_OBFS4;
+import static org.briarproject.mailbox.core.tor.TorConstants.BRIDGE_USE_OBFS4_DEFAULT;
+import static org.briarproject.mailbox.core.tor.TorConstants.BRIDGE_USE_SNOWFLAKE;
+import static org.briarproject.mailbox.core.tor.TorConstants.BRIDGE_USE_VANILLA;
 import static org.briarproject.mailbox.core.tor.TorConstants.HS_ADDRESS_V3;
 import static org.briarproject.mailbox.core.tor.TorConstants.HS_PRIVATE_KEY_V3;
 import static org.briarproject.mailbox.core.tor.TorConstants.SETTINGS_NAMESPACE;
 import static org.briarproject.mailbox.core.util.LogUtils.info;
 import static org.briarproject.mailbox.core.util.LogUtils.logException;
 import static org.briarproject.mailbox.core.util.PrivacyUtils.scrubOnion;
+import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.MEEK;
+import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
+import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.VANILLA;
 import static org.slf4j.LoggerFactory.getLogger;
 
 public abstract class AbstractTorPlugin implements TorPlugin, EventListener {
@@ -89,6 +99,8 @@ public abstract class AbstractTorPlugin implements TorPlugin, EventListener {
 
 	protected final PluginState state = new PluginState();
 
+	private volatile Settings settings = null;
+
 	AbstractTorPlugin(Executor ioExecutor,
 			SettingsManager settingsManager,
 			NetworkManager networkManager,
@@ -139,6 +151,13 @@ public abstract class AbstractTorPlugin implements TorPlugin, EventListener {
 	@Override
 	public void startService() throws ServiceException {
 		if (used.getAndSet(true)) throw new IllegalStateException();
+		// Load the settings
+		try {
+			settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
+		} catch (DbException e) {
+			logException(LOG, e, "Error while retrieving settings");
+			settings = new Settings();
+		}
 		// Start Tor
 		try {
 			tor.start();
@@ -168,14 +187,7 @@ public abstract class AbstractTorPlugin implements TorPlugin, EventListener {
 	private void publishHiddenService(int port) {
 		if (!tor.isTorRunning()) return;
 
-		Settings s;
-		try {
-			s = settingsManager.getSettings(SETTINGS_NAMESPACE);
-		} catch (DbException e) {
-			logException(LOG, e, "Error while retrieving settings");
-			s = new Settings();
-		}
-		String privateKey3 = s.get(HS_PRIVATE_KEY_V3);
+		String privateKey3 = settings.get(HS_PRIVATE_KEY_V3);
 		createV3HiddenService(port, privateKey3);
 	}
 
@@ -197,6 +209,8 @@ public abstract class AbstractTorPlugin implements TorPlugin, EventListener {
 			s.put(HS_PRIVATE_KEY_V3, hsProps.privKey);
 			try {
 				settingsManager.mergeSettings(s, SETTINGS_NAMESPACE);
+				// update cached settings with merge result
+				settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
 			} catch (DbException e) {
 				logException(LOG, e, "Error while merging settings");
 			}
@@ -204,9 +218,8 @@ public abstract class AbstractTorPlugin implements TorPlugin, EventListener {
 	}
 
 	@Nullable
-	public String getHiddenServiceAddress() throws DbException {
-		Settings s = settingsManager.getSettings(SETTINGS_NAMESPACE);
-		return s.get(HS_ADDRESS_V3);
+	public String getHiddenServiceAddress() {
+		return settings == null ? null : settings.get(HS_ADDRESS_V3);
 	}
 
 	private void enableBridges(List<BridgeType> bridgeTypes, String countryCode)
@@ -240,6 +253,20 @@ public abstract class AbstractTorPlugin implements TorPlugin, EventListener {
 		}
 	}
 
+	@Override
+	public void onSettingsChanged() {
+		ioExecutor.execute(() -> {
+			try {
+				settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
+			} catch (DbException e) {
+				logException(LOG, e, "Error while retrieving settings");
+				settings = new Settings();
+			}
+			// TODO reset actual plugin state, if this causes us to lose conn
+			updateConnectionStatus(networkManager.getNetworkStatus());
+		});
+	}
+
 	private void updateConnectionStatus(NetworkStatus status) {
 		connectionStatusExecutor.execute(() -> {
 			if (!tor.isTorRunning()) return;
@@ -247,7 +274,6 @@ public abstract class AbstractTorPlugin implements TorPlugin, EventListener {
 			boolean wifi = status.isWifi();
 			boolean ipv6Only = status.isIpv6Only();
 			String country = locationUtils.getCurrentCountry();
-			boolean bridgesWork = circumventionProvider.doBridgesWork(country);
 
 			if (LOG.isInfoEnabled()) {
 				LOG.info("Online: " + online + ", wifi: " + wifi
@@ -264,19 +290,7 @@ public abstract class AbstractTorPlugin implements TorPlugin, EventListener {
 			} else {
 				LOG.info("Enabling network");
 				enableNetwork = true;
-				if (bridgesWork) {
-					if (ipv6Only) {
-						bridgeTypes = asList(MEEK, SNOWFLAKE);
-					} else {
-						bridgeTypes = circumventionProvider
-								.getSuitableBridgeTypes(country);
-					}
-					if (LOG.isInfoEnabled()) {
-						LOG.info("Using bridge types " + bridgeTypes);
-					}
-				} else {
-					LOG.info("Not using bridges");
-				}
+				bridgeTypes = getBridgeTypes(country, ipv6Only);
 				if (wifi) {
 					LOG.info("Enabling connection padding");
 					enableConnectionPadding = true;
@@ -298,6 +312,50 @@ public abstract class AbstractTorPlugin implements TorPlugin, EventListener {
 		});
 	}
 
+	private List<BridgeType> getBridgeTypes(String country, boolean ipv6Only) {
+		List<BridgeType> bridgeTypes = emptyList();
+		boolean bridgesNeeded =
+				circumventionProvider.doBridgesWork(country);
+		boolean bridgeAuto = settings.getBoolean(BRIDGE_AUTO, true);
+		if (bridgeAuto) {
+			if (bridgesNeeded) {
+				if (ipv6Only) {
+					bridgeTypes = asList(MEEK, SNOWFLAKE);
+				} else {
+					bridgeTypes = circumventionProvider
+							.getSuitableBridgeTypes(country);
+				}
+				if (LOG.isInfoEnabled()) {
+					LOG.info("Using bridge types " + bridgeTypes);
+				}
+			} else {
+				LOG.info("Not using bridges");
+			}
+		} else {
+			boolean useBridges = settings.getBoolean(BRIDGE_USE, false);
+			if (useBridges) {
+				ArrayList<BridgeType> types = new ArrayList<>();
+				if (settings.getBoolean(BRIDGE_USE_SNOWFLAKE, false))
+					types.add(SNOWFLAKE);
+				if (settings.getBoolean(BRIDGE_USE_MEEK, false))
+					types.add(MEEK);
+				if (settings.getBoolean(BRIDGE_USE_OBFS4, false))
+					types.add(NON_DEFAULT_OBFS4);
+				if (settings.getBoolean(BRIDGE_USE_OBFS4_DEFAULT, false))
+					types.add(DEFAULT_OBFS4);
+				if (settings.getBoolean(BRIDGE_USE_VANILLA, false))
+					types.add(VANILLA);
+				bridgeTypes = types;
+				if (LOG.isInfoEnabled()) {
+					LOG.info("Using bridge types " + bridgeTypes);
+				}
+			} else {
+				LOG.info("Not using bridges");
+			}
+		}
+		return bridgeTypes;
+	}
+
 	@ThreadSafe
 	private class PluginState {
 
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java
index ca6b6633ecfe7eb554e85c5016f63bf91271f57d..cb463466749c52387e84c7f15191496a594e6057 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java
@@ -25,6 +25,20 @@ public interface TorConstants {
 	String SETTINGS_NAMESPACE = "Tor";
 	String HS_PRIVATE_KEY_V3 = "onionPrivKey3";
 	String HS_ADDRESS_V3 = "onionAddress3";
+	/**
+	 * Whether circumvention bridge handling should be handled automatically.
+	 */
+	String BRIDGE_AUTO = "bridgeAuto";
+	/**
+	 * Whether bridges should be used for circumvention.
+	 * Only consider when {@link #BRIDGE_AUTO} is false.
+	 */
+	String BRIDGE_USE = "bridgeUse";
+	String BRIDGE_USE_SNOWFLAKE = "bridgeUseSnowflake";
+	String BRIDGE_USE_MEEK = "bridgeUseMeek";
+	String BRIDGE_USE_OBFS4 = "bridgeUseObfs4";
+	String BRIDGE_USE_OBFS4_DEFAULT = "bridgeUseObfs4Default";
+	String BRIDGE_USE_VANILLA = "bridgeUseVanilla";
 
 	int SOCKS_PORT = 59054;
 	int CONTROL_PORT = 59055;
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java
index bbc28f42779005a40bd06206cb6c34c8939a2c85..851ce75fbb249ecc244220d901aea960a1e7b9d2 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java
@@ -2,6 +2,7 @@ package org.briarproject.mailbox.core.tor;
 
 import org.briarproject.mailbox.core.db.DbException;
 import org.briarproject.mailbox.core.lifecycle.Service;
+import org.briarproject.mailbox.core.settings.Settings;
 
 import kotlinx.coroutines.flow.StateFlow;
 
@@ -9,6 +10,16 @@ public interface TorPlugin extends Service {
 
 	StateFlow<TorPluginState> getState();
 
+	/**
+	 * Call this whenever {@link Settings} in
+	 * {@link TorConstants#SETTINGS_NAMESPACE} have changed.
+	 */
+	void onSettingsChanged();
+
+	/**
+	 * This is only available after {@link #startService()} has returned.
+	 * Otherwise returns null.
+	 */
 	String getHiddenServiceAddress() throws DbException;
 
 }
diff --git a/mailbox-lib/src/main/java/org/briarproject/mailbox/core/tor/FakeTorPlugin.kt b/mailbox-lib/src/main/java/org/briarproject/mailbox/core/tor/FakeTorPlugin.kt
index d56ec5cfb1c9207dba95387effb3d229e71b87fc..95b2db49cca108e1a58b0ca329567b38b60dd5eb 100644
--- a/mailbox-lib/src/main/java/org/briarproject/mailbox/core/tor/FakeTorPlugin.kt
+++ b/mailbox-lib/src/main/java/org/briarproject/mailbox/core/tor/FakeTorPlugin.kt
@@ -20,5 +20,8 @@ class FakeTorPlugin @Inject constructor() : TorPlugin {
         return state
     }
 
+    override fun onSettingsChanged() {
+    }
+
     override fun getHiddenServiceAddress(): String? = null
 }