diff --git a/src/core/config/check.rs b/src/core/config/check.rs
index a631c4f8dec4b8457d27ad2ee1497bcc9be8c8e1..403aa27c746daeac7664ab04c9a19a8d4b1a3b6f 100644
--- a/src/core/config/check.rs
+++ b/src/core/config/check.rs
@@ -27,46 +27,43 @@ pub fn check(config: &Config) -> Result<(), Error> {
 		));
 	}
 
-	if config.address.is_loopback() && cfg!(unix) {
-		debug!(
-			"Found loopback listening address {}, running checks if we're in a container.",
-			config.address
-		);
-
-		#[cfg(unix)]
-		if Path::new("/proc/vz").exists() /* Guest */ && !Path::new("/proc/bz").exists()
-		/* Host */
-		{
-			error!(
-				"You are detected using OpenVZ with a loopback/localhost listening address of {}. If you are using \
-				 OpenVZ for containers and you use NAT-based networking to communicate with the host and guest, this \
-				 will NOT work. Please change this to \"0.0.0.0\". If this is expected, you can ignore.",
-				config.address
-			);
-		}
-
-		#[cfg(unix)]
-		if Path::new("/.dockerenv").exists() {
-			error!(
-				"You are detected using Docker with a loopback/localhost listening address of {}. If you are using a \
-				 reverse proxy on the host and require communication to conduwuit in the Docker container via \
-				 NAT-based networking, this will NOT work. Please change this to \"0.0.0.0\". If this is expected, \
-				 you can ignore.",
-				config.address
-			);
+	config.get_bind_addrs().iter().for_each(|addr| {
+		if addr.ip().is_loopback() && cfg!(unix) {
+			debug!("Found loopback listening address {addr}, running checks if we're in a container.",);
+
+			#[cfg(unix)]
+			if Path::new("/proc/vz").exists() /* Guest */ && !Path::new("/proc/bz").exists()
+			/* Host */
+			{
+				error!(
+					"You are detected using OpenVZ with a loopback/localhost listening address of {addr}. If you are \
+					 using OpenVZ for containers and you use NAT-based networking to communicate with the host and \
+					 guest, this will NOT work. Please change this to \"0.0.0.0\". If this is expected, you can \
+					 ignore.",
+				);
+			}
+
+			#[cfg(unix)]
+			if Path::new("/.dockerenv").exists() {
+				error!(
+					"You are detected using Docker with a loopback/localhost listening address of {addr}. If you are \
+					 using a reverse proxy on the host and require communication to conduwuit in the Docker container \
+					 via NAT-based networking, this will NOT work. Please change this to \"0.0.0.0\". If this is \
+					 expected, you can ignore.",
+				);
+			}
+
+			#[cfg(unix)]
+			if Path::new("/run/.containerenv").exists() {
+				error!(
+					"You are detected using Podman with a loopback/localhost listening address of {addr}. If you are \
+					 using a reverse proxy on the host and require communication to conduwuit in the Podman container \
+					 via NAT-based networking, this will NOT work. Please change this to \"0.0.0.0\". If this is \
+					 expected, you can ignore.",
+				);
+			}
 		}
-
-		#[cfg(unix)]
-		if Path::new("/run/.containerenv").exists() {
-			error!(
-				"You are detected using Podman with a loopback/localhost listening address of {}. If you are using a \
-				 reverse proxy on the host and require communication to conduwuit in the Podman container via \
-				 NAT-based networking, this will NOT work. Please change this to \"0.0.0.0\". If this is expected, \
-				 you can ignore.",
-				config.address
-			);
-		}
-	}
+	});
 
 	// rocksdb does not allow max_log_files to be 0
 	if config.rocksdb_max_log_files == 0 && cfg!(feature = "rocksdb") {
diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs
index bb13923beb37369a8ec84a317c573de5c2357933..67b8397505f9705ead01383ec1fe18e3d174d5b2 100644
--- a/src/core/config/mod.rs
+++ b/src/core/config/mod.rs
@@ -1,7 +1,7 @@
 use std::{
 	collections::BTreeMap,
 	fmt::{self, Write as _},
-	net::{IpAddr, Ipv6Addr, SocketAddr},
+	net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
 	path::PathBuf,
 };
 
@@ -36,13 +36,20 @@ struct ListeningPort {
 	ports: Either<u16, Vec<u16>>,
 }
 
+#[derive(Deserialize, Clone, Debug)]
+#[serde(transparent)]
+struct ListeningAddr {
+	#[serde(with = "either::serde_untagged")]
+	addrs: Either<IpAddr, Vec<IpAddr>>,
+}
+
 /// all the config options for conduwuit
 #[derive(Clone, Debug, Deserialize)]
 #[allow(clippy::struct_excessive_bools)]
 pub struct Config {
 	/// [`IpAddr`] conduwuit will listen on (can be IPv4 or IPv6)
 	#[serde(default = "default_address")]
-	pub address: IpAddr,
+	address: ListeningAddr,
 	/// default TCP port(s) conduwuit will listen on
 	#[serde(default = "default_port")]
 	port: ListeningPort,
@@ -471,22 +478,27 @@ fn is_dual_listening(raw_config: &Figment) -> bool {
 
 	#[must_use]
 	pub fn get_bind_addrs(&self) -> Vec<SocketAddr> {
+		let mut addrs = Vec::new();
+		for host in &self.get_bind_hosts() {
+			for port in &self.get_bind_ports() {
+				addrs.push(SocketAddr::new(*host, *port));
+			}
+		}
+
+		addrs
+	}
+
+	fn get_bind_hosts(&self) -> Vec<IpAddr> {
+		match &self.address.addrs {
+			Left(addr) => vec![*addr],
+			Right(addrs) => addrs.clone(),
+		}
+	}
+
+	fn get_bind_ports(&self) -> Vec<u16> {
 		match &self.port.ports {
-			Left(port) => {
-				// Left is only 1 value, so make a vec with 1 value only
-				let port_vec = [port];
-
-				port_vec
-					.iter()
-					.copied()
-					.map(|port| SocketAddr::from((self.address, *port)))
-					.collect::<Vec<_>>()
-			},
-			Right(ports) => ports
-				.iter()
-				.copied()
-				.map(|port| SocketAddr::from((self.address, port)))
-				.collect::<Vec<_>>(),
+			Left(port) => vec![*port],
+			Right(ports) => ports.clone(),
 		}
 	}
 
@@ -875,7 +887,11 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 
 fn true_fn() -> bool { true }
 
-fn default_address() -> IpAddr { Ipv6Addr::LOCALHOST.into() }
+fn default_address() -> ListeningAddr {
+	ListeningAddr {
+		addrs: Right(vec![Ipv4Addr::LOCALHOST.into(), Ipv6Addr::LOCALHOST.into()]),
+	}
+}
 
 fn default_port() -> ListeningPort {
 	ListeningPort {