diff --git a/rts/Capability.c b/rts/Capability.c
index c8e15c178d565e634591db8a86d5d4e2cd1729e1..a9fe393a08db207bab3a60b1c14dda5ac1ad7fd2 100644
--- a/rts/Capability.c
+++ b/rts/Capability.c
@@ -281,7 +281,7 @@ initCapability (Capability *cap, uint32_t i)
 #endif
     cap->total_allocated        = 0;
 
-    initCapabilityIOManager(&cap->iomgr);
+    initCapabilityIOManager(cap); /* initialises cap->iomgr */
 
     cap->f.stgEagerBlackholeInfo = (W_)&__stg_EAGER_BLACKHOLE_info;
     cap->f.stgGCEnter1     = (StgFunPtr)__stg_gc_enter_1;
diff --git a/rts/IOManager.c b/rts/IOManager.c
index e6db47fa50fe7fe3d421716a806daa2cb4367e0d..e0e4ef04ac2e207446e00926b0a18fe7c6f40182 100644
--- a/rts/IOManager.c
+++ b/rts/IOManager.c
@@ -174,8 +174,11 @@ parseIOManagerFlag(const char *iomgrstr, IO_MANAGER_FLAG *flag)
  *
  * This fills in the iomgr_type and rts_IOManagerIsWin32Native globals.
  * Must be called before the I/O manager is started.
+ *
+ * Called early in the RTS initialisation, after the RTS flags have been
+ * processed.
  */
-static void selectIOManager(void)
+void selectIOManager(void)
 {
     switch (RtsFlags.MiscFlags.ioManager) {
         case IO_MNGR_FLAG_AUTO:
@@ -242,9 +245,13 @@ static void selectIOManager(void)
 
 
 /* Allocate and initialise the per-capability CapIOManager that lives in each
- * Capability. Called early in the RTS initialisation.
+ * Capability. Called from initCapability(), which is done in the RTS startup
+ * in initCapabilities(), and later at runtime via setNumCapabilities().
+ *
+ * Note that during RTS startup this is called _before_ the storage manager
+ * is initialised, so this is not allowed to allocate on the GC heap.
  */
-void initCapabilityIOManager(CapIOManager **piomgr)
+void initCapabilityIOManager(Capability *cap)
 {
     CapIOManager *iomgr =
       (CapIOManager *) stgMallocBytes(sizeof(CapIOManager),
@@ -275,20 +282,18 @@ void initCapabilityIOManager(CapIOManager **piomgr)
             break;
     }
 
-    *piomgr = iomgr;
+    cap->iomgr = iomgr;
 }
 
 
 /* Called late in the RTS initialisation
  */
-void
-initIOManager(void)
+void initIOManager(void)
 {
-    selectIOManager();
 
     switch (iomgr_type) {
 
-        /* The IO_MANAGER_SELECT needs no initialisation */
+        /* The IO_MANAGER_SELECT needs no global initialisation */
 
 #if defined(IOMGR_ENABLED_MIO_POSIX)
         case IO_MANAGER_MIO_POSIX:
diff --git a/rts/IOManager.h b/rts/IOManager.h
index 24218e9315d061197cd0ee8005dad2cba5e3e8eb..b54dbd0974d1b8df15841a6c5909657b488744e3 100644
--- a/rts/IOManager.h
+++ b/rts/IOManager.h
@@ -214,11 +214,19 @@ typedef struct {
 } CapIOManager;
 
 
+/* Init hook: called from hs_init_ghc, early in the startup after the RTS flags
+ * have been processed.
+ *
+ * Based on the I/O manager RTS flag, select an I/O manager to use.
+ */
+void selectIOManager(void);
+
+
 /* Allocate and initialise the per-capability CapIOManager that lives in each
- * Capability. It is called from initCapability, via initScheduler,
- * via hs_init_ghc.
+ * Capability. Called from initCapability(), which is done in the RTS startup
+ * in initCapabilities(), and later at runtime via setNumCapabilities().
  */
-void initCapabilityIOManager(CapIOManager **iomgr);
+void initCapabilityIOManager(Capability *cap);
 
 
 /* Init hook: called from hs_init_ghc, very late in the startup after almost
diff --git a/rts/RtsStartup.c b/rts/RtsStartup.c
index cfbd0421f5d4f0c4c6948495310d417cf9e3ed0f..70e926b44623fe9a65fd6df683e7b8cd93736059 100644
--- a/rts/RtsStartup.c
+++ b/rts/RtsStartup.c
@@ -331,6 +331,9 @@ hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config)
 #endif /* DEBUG */
     }
 
+    /* Based on the RTS flags, decide which I/O manager to use. */
+    selectIOManager();
+
     /* Initialize console Codepage.  */
 #if defined(mingw32_HOST_OS)
    if (is_io_mng_native_p())