diff --git a/ghcup.cabal b/ghcup.cabal
index d8a1cddc8af54428a6c675d667e62e80561d81a4..1213069924db1d816cc3aa030df07dc7f41a6277 100644
--- a/ghcup.cabal
+++ b/ghcup.cabal
@@ -21,6 +21,11 @@ source-repository head
   type:     git
   location: https://github.com/hasufell/ghcup-hs
 
+flag Curl
+  description: Use curl instead of http-io-streams for download
+  default:     False
+  manual:      True
+
 common HsOpenSSL
   build-depends: HsOpenSSL >=0.11.4.18
 
@@ -238,8 +243,6 @@ library
     , hpath-filepath
     , hpath-io
     , hpath-posix
-    , http-io-streams
-    , io-streams
     , language-bash
     , lzma
     , monad-logger
@@ -259,7 +262,6 @@ library
     , string-interpolate
     , tar-bytestring
     , template-haskell
-    , terminal-progress-bar
     , text
     , time
     , transformers
@@ -277,6 +279,7 @@ library
   exposed-modules:
     GHCup
     GHCup.Download
+    GHCup.Download.Utils
     GHCup.Errors
     GHCup.Platform
     GHCup.Types
@@ -296,6 +299,15 @@ library
   -- other-extensions:
   hs-source-dirs:  lib
 
+  if !flag(curl)
+    import:
+      , http-io-streams
+      , io-streams
+      , terminal-progress-bar
+    exposed-modules: GHCup.Download.IOStreams
+  else
+    cpp-options:     -DCURL
+
 executable ghcup
   import:
     config
diff --git a/lib/GHCup/Download.hs b/lib/GHCup/Download.hs
index 9fa2349bd7e0f0b3468421e8b3238d15f160defe..f51a58076e757ca53e4d3a710c3f0fe373d43d56 100644
--- a/lib/GHCup/Download.hs
+++ b/lib/GHCup/Download.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP                   #-}
 {-# LANGUAGE DataKinds             #-}
 {-# LANGUAGE DeriveGeneric         #-}
 {-# LANGUAGE FlexibleContexts      #-}
@@ -10,15 +11,21 @@
 
 module GHCup.Download where
 
-
+#if !defined(CURL)
+import           GHCup.Download.IOStreams
+import           GHCup.Download.Utils
+#endif
 import           GHCup.Errors
 import           GHCup.Platform
 import           GHCup.Types
 import           GHCup.Types.JSON               ( )
 import           GHCup.Types.Optics
 import           GHCup.Utils
+#if defined(CURL)
 import           GHCup.Utils.File
+#endif
 import           GHCup.Utils.Prelude
+import           GHCup.Version
 
 import           Control.Applicative
 import           Control.Exception.Safe
@@ -29,12 +36,9 @@ import           Control.Monad.Trans.Resource
                                          hiding ( throwM )
 import           Data.Aeson
 import           Data.ByteString                ( ByteString )
-import           Data.ByteString.Builder
 import           Data.CaseInsensitive           ( CI )
-import           Data.IORef
 import           Data.Maybe
 import           Data.String.Interpolate
-import           Data.Text.Read
 import           Data.Time.Clock
 import           Data.Time.Clock.POSIX
 import           Data.Time.Format
@@ -43,7 +47,6 @@ import           GHC.IO.Exception
 import           HPath
 import           HPath.IO                      as HIO
 import           Haskus.Utils.Variant.Excepts
-import           Network.Http.Client     hiding ( URL )
 import           OpenSSL.Digest
 import           Optics
 import           Prelude                 hiding ( abs
@@ -51,30 +54,19 @@ import           Prelude                 hiding ( abs
                                                 , writeFile
                                                 )
 import           System.IO.Error
-import "unix"    System.Posix.IO.ByteString
-                                         hiding ( fdWrite )
-import "unix-bytestring" System.Posix.IO.ByteString
-                                                ( fdWrite )
-import           System.ProgressBar
 import           URI.ByteString
-import           URI.ByteString.QQ
 
-import qualified Data.Binary.Builder           as B
-import qualified Data.ByteString               as BS
 import qualified Data.ByteString.Lazy          as L
 import qualified Data.CaseInsensitive          as CI
 import qualified Data.Map.Strict               as M
 import qualified Data.Text                     as T
 import qualified Data.Text.Encoding            as E
-import qualified System.IO.Streams             as Streams
 import qualified System.Posix.Files.ByteString as PF
 import qualified System.Posix.RawFilePath.Directory
                                                as RD
 
 
 
-ghcupURL :: URI
-ghcupURL = [uri|https://www.haskell.org/ghcup/data/ghcup-0.0.1.json|]
 
 
 
@@ -126,6 +118,7 @@ getDownloads urlSource = do
                 , UnsupportedScheme
                 , NoLocationHeader
                 , TooManyRedirs
+                , ProcessError
                 ]
                m1
                L.ByteString
@@ -154,7 +147,7 @@ getDownloads urlSource = do
                     pure bs
                   else liftIO $ readFile json_file
               Nothing -> do
-                lift $ $(logWarn) [i|Unable to get/parse Last-Modified header|]
+                lift $ $(logDebug) [i|Unable to get/parse Last-Modified header|]
                 liftIO $ deleteFile json_file
                 liftE $ downloadBS uri'
           else -- access in less than 5 minutes, re-use file
@@ -167,11 +160,14 @@ getDownloads urlSource = do
             liftIO $ writeFileWithModTime modTime json_file bs
             pure bs
           Nothing -> do
-            lift $ $(logWarn) [i|Unable to get/parse Last-Modified header|]
+            lift $ $(logDebug) [i|Unable to get/parse Last-Modified header|]
             liftE $ downloadBS uri'
 
    where
     getModTime = do
+#if defined(CURL)
+      pure Nothing
+#else
       headers <-
         handleIO (\_ -> pure mempty)
         $ liftE
@@ -182,7 +178,7 @@ getDownloads urlSource = do
           $ getHead uri'
           )
       pure $ parseModifiedHeader headers
-
+#endif
 
   parseModifiedHeader :: (M.Map (CI ByteString) ByteString) -> Maybe UTCTime
   parseModifiedHeader headers =
@@ -285,25 +281,25 @@ download dli dest mfn
     let uri' = E.decodeUtf8 (serializeURIRef' (view dlUri dli))
     lift $ $(logInfo) [i|downloading: #{uri'}|]
 
-    (https, host, fullPath, port) <- reThrowAll DownloadFailed
-      $ uriToQuadruple (view dlUri dli)
-
     -- destination dir must exist
     liftIO $ hideError AlreadyExists $ createDirRecursive newDirPerms dest
     destFile <- getDestFile
 
     -- download
-    fd       <- liftIO $ createRegularFileFd newFilePerms destFile
-    let stepper = fdWrite fd
     flip onException
          (liftIO $ hideError doesNotExistErrorType $ deleteFile destFile)
-      $ flip finally (liftIO $ closeFd fd)
-      $ catchAllE
+     $ catchAllE @_ @'[ProcessError, DownloadFailed, UnsupportedScheme]
           (\e ->
             (liftIO $ hideError doesNotExistErrorType $ deleteFile destFile)
               >> (throwE . DownloadFailed $ e)
-          )
-      $ downloadInternal True https host fullPath port stepper
+          ) $ do
+#if defined(CURL)
+              liftE $ lEM @_ @'[ProcessError] $ liftIO $ exec "curl" True
+                ["-sSfL", "-o", toFilePath destFile , serializeURIRef' $ view dlUri dli] Nothing Nothing
+#else
+              (https, host, fullPath, port) <- liftE $ uriToQuadruple (view dlUri dli)
+              liftE $ downloadToFile https host fullPath port destFile
+#endif
 
     liftE $ checkDigest dli destFile
     pure destFile
@@ -352,6 +348,8 @@ downloadCached dli mfn = do
     ------------------
 
 
+
+
 -- | This is used for downloading the JSON.
 downloadBS :: (MonadCatch m, MonadIO m)
            => URI
@@ -362,6 +360,7 @@ downloadBS :: (MonadCatch m, MonadIO m)
                  , UnsupportedScheme
                  , NoLocationHeader
                  , TooManyRedirs
+                 , ProcessError
                  ]
                 m
                 L.ByteString
@@ -380,220 +379,17 @@ downloadBS uri'
   scheme = view (uriSchemeL' % schemeBSL') uri'
   path   = view pathL' uri'
   dl https = do
+#if defined(CURL)
+    let exe = [rel|curl|]
+        args = ["-sSfL", serializeURIRef' uri']
+    liftIO (executeOut exe args Nothing) >>= \case
+      CapturedProcess ExitSuccess stdout _ -> do
+        pure $ L.fromStrict stdout
+      CapturedProcess (ExitFailure i') _ _ -> throwE $ NonZeroExit i' (toFilePath exe) args
+#else
     (_, host', fullPath', port') <- liftE $ uriToQuadruple uri'
     liftE $ downloadBS' https host' fullPath' port'
-
-
--- | Load the result of this download into memory at once.
-downloadBS' :: MonadIO m
-            => Bool             -- ^ https?
-            -> ByteString       -- ^ host (e.g. "www.example.com")
-            -> ByteString       -- ^ path (e.g. "/my/file") including query
-            -> Maybe Int        -- ^ optional port (e.g. 3000)
-            -> Excepts
-                 '[ HTTPStatusError
-                  , URIParseError
-                  , UnsupportedScheme
-                  , NoLocationHeader
-                  , TooManyRedirs
-                  ]
-                 m
-                 (L.ByteString)
-downloadBS' https host path port = do
-  bref <- liftIO $ newIORef (mempty :: Builder)
-  let stepper bs = modifyIORef bref (<> byteString bs)
-  downloadInternal False https host path port stepper
-  liftIO (readIORef bref <&> toLazyByteString)
-
-
-downloadInternal :: MonadIO m
-                 => Bool        -- ^ whether to show a progress bar
-                 -> Bool        -- ^ https?
-                 -> ByteString  -- ^ host
-                 -> ByteString  -- ^ path with query
-                 -> Maybe Int   -- ^ optional port
-                 -> (ByteString -> IO a)   -- ^ the consuming step function
-                 -> Excepts
-                      '[ HTTPStatusError
-                       , URIParseError
-                       , UnsupportedScheme
-                       , NoLocationHeader
-                       , TooManyRedirs
-                       ]
-                      m
-                      ()
-downloadInternal = go (5 :: Int)
-
- where
-  go redirs progressBar https host path port consumer = do
-    r <- liftIO $ withConnection' https host port action
-    veitherToExcepts r >>= \case
-      Just r' ->
-        if redirs > 0 then followRedirectURL r' else throwE TooManyRedirs
-      Nothing -> pure ()
-   where
-    action c = do
-      let q = buildRequest1 $ http GET path
-
-      sendRequest c q emptyBody
-
-      receiveResponse
-        c
-        (\r i' -> runE $ do
-          let scode = getStatusCode r
-          if
-            | scode >= 200 && scode < 300 -> downloadStream r i' >> pure Nothing
-            | scode >= 300 && scode < 400 -> case getHeader r "Location" of
-              Just r' -> pure $ Just $ r'
-              Nothing -> throwE NoLocationHeader
-            | otherwise -> throwE $ HTTPStatusError scode
-        )
-
-    followRedirectURL bs = case parseURI strictURIParserOptions bs of
-      Right uri' -> do
-        (https', host', fullPath', port') <- liftE $ uriToQuadruple uri'
-        go (redirs - 1) progressBar https' host' fullPath' port' consumer
-      Left e -> throwE e
-
-    downloadStream r i' = do
-      let size = case getHeader r "Content-Length" of
-            Just x' -> case decimal $ E.decodeUtf8 x' of
-              Left  _       -> 0
-              Right (r', _) -> r'
-            Nothing -> 0
-
-      mpb <- if progressBar
-        then Just <$> (liftIO $ newProgressBar defStyle 10 (Progress 0 size ()))
-        else pure Nothing
-
-      outStream <- liftIO $ Streams.makeOutputStream
-        (\case
-          Just bs -> do
-            forM_ mpb $ \pb -> incProgress pb (BS.length bs)
-            void $ consumer bs
-          Nothing -> pure ()
-        )
-      liftIO $ Streams.connect i' outStream
-
-
-
-getHead :: (MonadCatch m, MonadIO m)
-        => URI
-        -> Excepts
-             '[ HTTPStatusError
-              , URIParseError
-              , UnsupportedScheme
-              , NoLocationHeader
-              , TooManyRedirs
-              ]
-             m
-             (M.Map (CI ByteString) ByteString)
-getHead uri' | scheme == "https" = head' True
-             | scheme == "http"  = head' False
-             | otherwise         = throwE UnsupportedScheme
-
- where
-  scheme = view (uriSchemeL' % schemeBSL') uri'
-  head' https = do
-    (_, host', fullPath', port') <- liftE $ uriToQuadruple uri'
-    liftE $ headInternal https host' fullPath' port'
-
-
-
-headInternal :: MonadIO m
-             => Bool        -- ^ https?
-             -> ByteString  -- ^ host
-             -> ByteString  -- ^ path with query
-             -> Maybe Int   -- ^ optional port
-             -> Excepts
-                  '[ HTTPStatusError
-                   , URIParseError
-                   , UnsupportedScheme
-                   , TooManyRedirs
-                   , NoLocationHeader
-                   ]
-                  m
-                  (M.Map (CI ByteString) ByteString)
-headInternal = go (5 :: Int)
-
- where
-  go redirs https host path port = do
-    r <- liftIO $ withConnection' https host port action
-    veitherToExcepts r >>= \case
-      Left r' ->
-        if redirs > 0 then followRedirectURL r' else throwE TooManyRedirs
-      Right hs -> pure hs
-   where
-
-    action c = do
-      let q = buildRequest1 $ http HEAD path
-
-      sendRequest c q emptyBody
-
-      unsafeReceiveResponse
-        c
-        (\r _ -> runE $ do
-          let scode = getStatusCode r
-          if
-            | scode >= 200 && scode < 300 -> do
-              let headers = getHeaderMap r
-              pure $ Right $ headers
-            | scode >= 300 && scode < 400 -> case getHeader r "Location" of
-              Just r' -> pure $ Left $ r'
-              Nothing -> throwE NoLocationHeader
-            | otherwise -> throwE $ HTTPStatusError scode
-        )
-
-    followRedirectURL bs = case parseURI strictURIParserOptions bs of
-      Right uri' -> do
-        (https', host', fullPath', port') <- liftE $ uriToQuadruple uri'
-        go (redirs - 1) https' host' fullPath' port'
-      Left e -> throwE e
-
-
-withConnection' :: Bool
-                -> ByteString
-                -> Maybe Int
-                -> (Connection -> IO a)
-                -> IO a
-withConnection' https host port action = bracket acquire closeConnection action
-
- where
-  acquire = case https of
-    True -> do
-      ctx <- baselineContextSSL
-      openConnectionSSL ctx host (fromIntegral $ fromMaybe 443 port)
-    False -> openConnection host (fromIntegral $ fromMaybe 80 port)
-
-
--- | Extracts from a URI type: (https?, host, path+query, port)
-uriToQuadruple :: Monad m
-               => URI
-               -> Excepts
-                    '[UnsupportedScheme]
-                    m
-                    (Bool, ByteString, ByteString, Maybe Int)
-uriToQuadruple URI {..} = do
-  let scheme = view schemeBSL' uriScheme
-
-  host <-
-    preview (_Just % authorityHostL' % hostBSL') uriAuthority
-      ?? UnsupportedScheme
-
-  https <- if
-    | scheme == "https" -> pure True
-    | scheme == "http"  -> pure False
-    | otherwise         -> throwE UnsupportedScheme
-
-  let queryBS =
-        BS.intercalate "&"
-          . fmap (\(x, y) -> encodeQuery x <> "=" <> encodeQuery y)
-          $ (queryPairs uriQuery)
-      port =
-        preview (_Just % authorityPortL' % _Just % portNumberL') uriAuthority
-      fullpath = if BS.null queryBS then uriPath else uriPath <> "?" <> queryBS
-  pure (https, host, fullpath, port)
-  where encodeQuery = L.toStrict . B.toLazyByteString . urlEncodeQuery
+#endif
 
 
 checkDigest :: (MonadIO m, MonadLogger m, MonadReader Settings m)
@@ -609,3 +405,4 @@ checkDigest dli file = do
     let cDigest = E.decodeUtf8 . toHex . digest (digestByName "sha256") $ c
         eDigest = view dlHash dli
     when ((cDigest /= eDigest) && verify) $ throwE (DigestError cDigest eDigest)
+
diff --git a/lib/GHCup/Download/IOStreams.hs b/lib/GHCup/Download/IOStreams.hs
new file mode 100644
index 0000000000000000000000000000000000000000..8096c16c890a82e5bc9dfff0680a79a10526d74c
--- /dev/null
+++ b/lib/GHCup/Download/IOStreams.hs
@@ -0,0 +1,253 @@
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE DeriveGeneric         #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE QuasiQuotes           #-}
+{-# LANGUAGE TemplateHaskell       #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE TypeFamilies          #-}
+
+
+module GHCup.Download.IOStreams where
+
+
+import           GHCup.Download.Utils
+import           GHCup.Errors
+import           GHCup.Types.JSON               ( )
+import           GHCup.Utils.File
+import           GHCup.Utils.Prelude
+
+import           Control.Applicative
+import           Control.Exception.Safe
+import           Control.Monad
+import           Control.Monad.Reader
+import           Data.ByteString                ( ByteString )
+import           Data.ByteString.Builder
+import           Data.CaseInsensitive           ( CI )
+import           Data.IORef
+import           Data.Maybe
+import           Data.Text.Read
+import           HPath
+import           HPath.IO                      as HIO
+import           Haskus.Utils.Variant.Excepts
+import           Network.Http.Client     hiding ( URL )
+import           Optics
+import           Prelude                 hiding ( abs
+                                                , readFile
+                                                , writeFile
+                                                )
+import "unix"    System.Posix.IO.ByteString
+                                         hiding ( fdWrite )
+import "unix-bytestring" System.Posix.IO.ByteString
+                                                ( fdWrite )
+import           System.ProgressBar
+import           URI.ByteString
+
+import qualified Data.ByteString               as BS
+import qualified Data.ByteString.Lazy          as L
+import qualified Data.Map.Strict               as M
+import qualified Data.Text.Encoding            as E
+import qualified System.IO.Streams             as Streams
+
+
+
+
+
+    ----------------------------
+    --[ Low-level (non-curl) ]--
+    ----------------------------
+
+
+-- | Load the result of this download into memory at once.
+downloadBS' :: MonadIO m
+            => Bool             -- ^ https?
+            -> ByteString       -- ^ host (e.g. "www.example.com")
+            -> ByteString       -- ^ path (e.g. "/my/file") including query
+            -> Maybe Int        -- ^ optional port (e.g. 3000)
+            -> Excepts
+                 '[ HTTPStatusError
+                  , URIParseError
+                  , UnsupportedScheme
+                  , NoLocationHeader
+                  , TooManyRedirs
+                  ]
+                 m
+                 (L.ByteString)
+downloadBS' https host path port = do
+  bref <- liftIO $ newIORef (mempty :: Builder)
+  let stepper bs = modifyIORef bref (<> byteString bs)
+  downloadInternal False https host path port stepper
+  liftIO (readIORef bref <&> toLazyByteString)
+
+
+downloadToFile :: (MonadMask m, MonadIO m)
+               => Bool             -- ^ https?
+               -> ByteString       -- ^ host (e.g. "www.example.com")
+               -> ByteString       -- ^ path (e.g. "/my/file") including query
+               -> Maybe Int        -- ^ optional port (e.g. 3000)
+               -> Path Abs         -- ^ destination file to create and write to
+               -> Excepts '[DownloadFailed] m ()
+downloadToFile https host fullPath port destFile = do
+  fd <- liftIO $ createRegularFileFd newFilePerms destFile
+  let stepper = fdWrite fd
+  flip finally (liftIO $ closeFd fd)
+    $ reThrowAll DownloadFailed $ downloadInternal True https host fullPath port stepper
+
+
+downloadInternal :: MonadIO m
+                 => Bool        -- ^ whether to show a progress bar
+                 -> Bool        -- ^ https?
+                 -> ByteString  -- ^ host
+                 -> ByteString  -- ^ path with query
+                 -> Maybe Int   -- ^ optional port
+                 -> (ByteString -> IO a)   -- ^ the consuming step function
+                 -> Excepts
+                      '[ HTTPStatusError
+                       , URIParseError
+                       , UnsupportedScheme
+                       , NoLocationHeader
+                       , TooManyRedirs
+                       ]
+                      m
+                      ()
+downloadInternal = go (5 :: Int)
+
+ where
+  go redirs progressBar https host path port consumer = do
+    r <- liftIO $ withConnection' https host port action
+    veitherToExcepts r >>= \case
+      Just r' ->
+        if redirs > 0 then followRedirectURL r' else throwE TooManyRedirs
+      Nothing -> pure ()
+   where
+    action c = do
+      let q = buildRequest1 $ http GET path
+
+      sendRequest c q emptyBody
+
+      receiveResponse
+        c
+        (\r i' -> runE $ do
+          let scode = getStatusCode r
+          if
+            | scode >= 200 && scode < 300 -> downloadStream r i' >> pure Nothing
+            | scode >= 300 && scode < 400 -> case getHeader r "Location" of
+              Just r' -> pure $ Just $ r'
+              Nothing -> throwE NoLocationHeader
+            | otherwise -> throwE $ HTTPStatusError scode
+        )
+
+    followRedirectURL bs = case parseURI strictURIParserOptions bs of
+      Right uri' -> do
+        (https', host', fullPath', port') <- liftE $ uriToQuadruple uri'
+        go (redirs - 1) progressBar https' host' fullPath' port' consumer
+      Left e -> throwE e
+
+    downloadStream r i' = do
+      let size = case getHeader r "Content-Length" of
+            Just x' -> case decimal $ E.decodeUtf8 x' of
+              Left  _       -> 0
+              Right (r', _) -> r'
+            Nothing -> 0
+
+      mpb <- if progressBar
+        then Just <$> (liftIO $ newProgressBar defStyle 10 (Progress 0 size ()))
+        else pure Nothing
+
+      outStream <- liftIO $ Streams.makeOutputStream
+        (\case
+          Just bs -> do
+            forM_ mpb $ \pb -> incProgress pb (BS.length bs)
+            void $ consumer bs
+          Nothing -> pure ()
+        )
+      liftIO $ Streams.connect i' outStream
+
+
+getHead :: (MonadCatch m, MonadIO m)
+        => URI
+        -> Excepts
+             '[ HTTPStatusError
+              , URIParseError
+              , UnsupportedScheme
+              , NoLocationHeader
+              , TooManyRedirs
+              , ProcessError
+              ]
+             m
+             (M.Map (CI ByteString) ByteString)
+getHead uri' | scheme == "https" = head' True
+             | scheme == "http"  = head' False
+             | otherwise         = throwE UnsupportedScheme
+
+ where
+  scheme = view (uriSchemeL' % schemeBSL') uri'
+  head' https = do
+    (_, host', fullPath', port') <- liftE $ uriToQuadruple uri'
+    liftE $ headInternal https host' fullPath' port'
+
+
+headInternal :: MonadIO m
+             => Bool        -- ^ https?
+             -> ByteString  -- ^ host
+             -> ByteString  -- ^ path with query
+             -> Maybe Int   -- ^ optional port
+             -> Excepts
+                  '[ HTTPStatusError
+                   , URIParseError
+                   , UnsupportedScheme
+                   , TooManyRedirs
+                   , NoLocationHeader
+                   ]
+                  m
+                  (M.Map (CI ByteString) ByteString)
+headInternal = go (5 :: Int)
+
+ where
+  go redirs https host path port = do
+    r <- liftIO $ withConnection' https host port action
+    veitherToExcepts r >>= \case
+      Left r' ->
+        if redirs > 0 then followRedirectURL r' else throwE TooManyRedirs
+      Right hs -> pure hs
+   where
+
+    action c = do
+      let q = buildRequest1 $ http HEAD path
+
+      sendRequest c q emptyBody
+
+      unsafeReceiveResponse
+        c
+        (\r _ -> runE $ do
+          let scode = getStatusCode r
+          if
+            | scode >= 200 && scode < 300 -> do
+              let headers = getHeaderMap r
+              pure $ Right $ headers
+            | scode >= 300 && scode < 400 -> case getHeader r "Location" of
+              Just r' -> pure $ Left $ r'
+              Nothing -> throwE NoLocationHeader
+            | otherwise -> throwE $ HTTPStatusError scode
+        )
+
+    followRedirectURL bs = case parseURI strictURIParserOptions bs of
+      Right uri' -> do
+        (https', host', fullPath', port') <- liftE $ uriToQuadruple uri'
+        go (redirs - 1) https' host' fullPath' port'
+      Left e -> throwE e
+
+
+withConnection' :: Bool
+                -> ByteString
+                -> Maybe Int
+                -> (Connection -> IO a)
+                -> IO a
+withConnection' https host port action = bracket acquire closeConnection action
+
+ where
+  acquire = case https of
+    True -> do
+      ctx <- baselineContextSSL
+      openConnectionSSL ctx host (fromIntegral $ fromMaybe 443 port)
+    False -> openConnection host (fromIntegral $ fromMaybe 80 port)
diff --git a/lib/GHCup/Download/Utils.hs b/lib/GHCup/Download/Utils.hs
new file mode 100644
index 0000000000000000000000000000000000000000..f9024700336cbb529546ffbef8cadf061bf668ed
--- /dev/null
+++ b/lib/GHCup/Download/Utils.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE DeriveGeneric         #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE QuasiQuotes           #-}
+{-# LANGUAGE TemplateHaskell       #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE TypeFamilies          #-}
+
+
+module GHCup.Download.Utils where
+
+
+import           GHCup.Errors
+import           GHCup.Types.Optics
+import           GHCup.Types.JSON               ( )
+import           GHCup.Utils.Prelude
+
+import           Control.Applicative
+import           Control.Monad
+import           Data.ByteString                ( ByteString )
+import           Data.Maybe
+import           Haskus.Utils.Variant.Excepts
+import           Optics
+import           Prelude                 hiding ( abs
+                                                , readFile
+                                                , writeFile
+                                                )
+import           URI.ByteString
+
+import qualified Data.Binary.Builder           as B
+import qualified Data.ByteString               as BS
+import qualified Data.ByteString.Lazy          as L
+
+
+-- | Extracts from a URI type: (https?, host, path+query, port)
+uriToQuadruple :: Monad m
+               => URI
+               -> Excepts
+                    '[UnsupportedScheme]
+                    m
+                    (Bool, ByteString, ByteString, Maybe Int)
+uriToQuadruple URI {..} = do
+  let scheme = view schemeBSL' uriScheme
+
+  host <-
+    preview (_Just % authorityHostL' % hostBSL') uriAuthority
+      ?? UnsupportedScheme
+
+  https <- if
+    | scheme == "https" -> pure True
+    | scheme == "http"  -> pure False
+    | otherwise         -> throwE UnsupportedScheme
+
+  let queryBS =
+        BS.intercalate "&"
+          . fmap (\(x, y) -> encodeQuery x <> "=" <> encodeQuery y)
+          $ (queryPairs uriQuery)
+      port =
+        preview (_Just % authorityPortL' % _Just % portNumberL') uriAuthority
+      fullpath = if BS.null queryBS then uriPath else uriPath <> "?" <> queryBS
+  pure (https, host, fullpath, port)
+  where encodeQuery = L.toStrict . B.toLazyByteString . urlEncodeQuery
+
diff --git a/lib/GHCup/Version.hs b/lib/GHCup/Version.hs
index a0713e422b04c909d51681f37d33864ce0cb9cf9..899f0e3afbf0a61962e700bbff393a1dcb7ab375 100644
--- a/lib/GHCup/Version.hs
+++ b/lib/GHCup/Version.hs
@@ -6,6 +6,11 @@ module GHCup.Version where
 import           GHCup.Utils.Version.QQ
 
 import           Data.Versions
+import           URI.ByteString
+import           URI.ByteString.QQ
+
+ghcupURL :: URI
+ghcupURL = [uri|https://www.haskell.org/ghcup/data/ghcup-0.0.1.json|]
 
 ghcUpVer :: PVP
 ghcUpVer = [pver|0.0.1|]