diff --git a/src/QBar/ControlSocket.hs b/src/QBar/ControlSocket.hs
index ef5de6977fb9f8b2b9963b3c1c7675cee58e955b..500b9dddd0055046b0c1411f3acee17c3d4b8150 100644
--- a/src/QBar/ControlSocket.hs
+++ b/src/QBar/ControlSocket.hs
@@ -9,6 +9,7 @@ import QBar.Core
 import QBar.Filter
 import QBar.BlockText
 
+import Control.Exception (handle)
 import Control.Monad (forever, void, when)
 import Control.Monad.STM (atomically)
 import Control.Concurrent (forkFinally)
@@ -17,6 +18,7 @@ import Control.Concurrent.STM.TChan (TChan, writeTChan)
 import Data.Aeson.TH
 import Data.ByteString (ByteString)
 import Data.Either (either)
+import Data.Maybe (maybe)
 import Data.Text.Lazy (Text, pack)
 import qualified Data.Text as T
 import qualified Data.Text.Lazy as TL
@@ -48,11 +50,24 @@ ipcSocketAddress MainOptions{socketLocation} = maybe defaultSocketPath (return .
   where
     defaultSocketPath :: IO FilePath
     defaultSocketPath = do
-      xdgRuntimeDir <- getEnv "XDG_RUNTIME_DIR"
-      waylandDisplay <- getEnv "WAYLAND_DISPLAY"
-      -- TODO: fallback to I3_SOCKET_PATH if WAYLAND_DISPLAY is not set.
-      -- If both are not set it might be useful to fall back to XDG_RUNTIME_DIR/qbar, so qbar can run headless (eg. for tests)
-      return $ xdgRuntimeDir </> waylandDisplay <> "-qbar"
+      waylandSocketPath' <- waylandSocketPath
+      maybe (maybe headlessSocketPath return =<< i3SocketPath) return waylandSocketPath'
+      where
+        waylandSocketPath :: IO (Maybe FilePath)
+        waylandSocketPath = handleEnvError $ do
+          xdgRuntimeDir <- getEnv "XDG_RUNTIME_DIR"
+          waylandDisplay <- getEnv "WAYLAND_DISPLAY"
+          return $ xdgRuntimeDir </> waylandDisplay <> "-qbar"
+        i3SocketPath :: IO (Maybe FilePath)
+        i3SocketPath = handleEnvError $ do
+          i3SocketPath' <- getEnv "I3_SOCKET_PATH"
+          return $ i3SocketPath' <> "-qbar"
+        headlessSocketPath :: IO FilePath
+        headlessSocketPath = do
+          xdgRuntimeDir <- getEnv "XDG_RUNTIME_DIR"
+          return $ xdgRuntimeDir </> "qbar"
+    handleEnvError :: IO FilePath -> IO (Maybe FilePath)
+    handleEnvError = handle (const $ return Nothing :: IOError -> IO (Maybe FilePath)) . fmap Just
 
 sendIpc :: MainOptions -> Command -> IO ()
 sendIpc options@MainOptions{verbose} request = do