diff --git a/src/node/cli.ts b/src/node/cli.ts
index 9eb6e5163e8a..60136913258c 100644
--- a/src/node/cli.ts
+++ b/src/node/cli.ts
@@ -12,6 +12,7 @@ export enum Feature {
 
 export enum AuthType {
   Password = "password",
+  HttpBasic = "http-basic",
   None = "none",
 }
 
@@ -65,6 +66,7 @@ export interface UserProvidedCodeArgs {
 export interface UserProvidedArgs extends UserProvidedCodeArgs {
   config?: string
   auth?: AuthType
+  "auth-user"?: string
   password?: string
   "hashed-password"?: string
   cert?: OptionalString
@@ -137,6 +139,10 @@ export type Options<T> = {
 
 export const options: Options<Required<UserProvidedArgs>> = {
   auth: { type: AuthType, description: "The type of authentication to use." },
+  "auth-user": {
+    type: "string",
+    description: "The username for http-basic authentication.",
+  },
   password: {
     type: "string",
     description: "The password for password authentication (can only be passed in via $PASSWORD or the config file).",
@@ -480,6 +486,7 @@ export interface DefaultedArgs extends ConfigArgs {
   "proxy-domain": string[]
   verbose: boolean
   usingEnvPassword: boolean
+  usingEnvAuthUser: boolean
   usingEnvHashedPassword: boolean
   "extensions-dir": string
   "user-data-dir": string
@@ -570,6 +577,14 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
     args.password = process.env.PASSWORD
   }
 
+  const usingEnvAuthUser = !!process.env.AUTH_USER
+  if (process.env.AUTH_USER) {
+    args["auth"] = AuthType.HttpBasic
+    args["auth-user"] = process.env.AUTH_USER
+  } else if (args["auth-user"]) {
+    args["auth"] = AuthType.HttpBasic
+  }
+
   if (process.env.CS_DISABLE_FILE_DOWNLOADS?.match(/^(1|true)$/)) {
     args["disable-file-downloads"] = true
   }
@@ -621,6 +636,7 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
   return {
     ...args,
     usingEnvPassword,
+    usingEnvAuthUser,
     usingEnvHashedPassword,
   } as DefaultedArgs // TODO: Technically no guarantee this is fulfilled.
 }
diff --git a/src/node/http.ts b/src/node/http.ts
index e0fb3a4caf6b..28419c6d6886 100644
--- a/src/node/http.ts
+++ b/src/node/http.ts
@@ -4,6 +4,7 @@ import * as expressCore from "express-serve-static-core"
 import * as http from "http"
 import * as net from "net"
 import * as qs from "qs"
+import safeCompare from "safe-compare"
 import { Disposable } from "../common/emitter"
 import { CookieKeys, HttpCode, HttpError } from "../common/http"
 import { normalize } from "../common/util"
@@ -20,6 +21,7 @@ import {
   escapeHtml,
   escapeJSON,
   splitOnFirstEquals,
+  isHashMatch,
 } from "./util"
 
 /**
@@ -111,6 +113,35 @@ export const ensureAuthenticated = async (
   }
 }
 
+/**
+ * Validate basic auth credentials.
+ */
+const validateBasicAuth = async (
+  authHeader: string | undefined,
+  authUser: string | undefined,
+  authPassword: string | undefined,
+  hashedPassword: string | undefined,
+): Promise<boolean> => {
+  if (!authHeader?.startsWith("Basic ")) {
+    return false
+  }
+
+  try {
+    const base64Credentials = authHeader.split(" ")[1]
+    const credentials = Buffer.from(base64Credentials, "base64").toString("utf-8")
+    const [username, password] = credentials.split(":")
+    if (username !== authUser) return false
+    if (hashedPassword) {
+      return await isHashMatch(password, hashedPassword)
+    } else {
+      return safeCompare(password, authPassword || "")
+    }
+  } catch (error) {
+    logger.error("Error validating basic auth:" + error)
+    return false
+  }
+}
+
 /**
  * Return true if authenticated via cookies.
  */
@@ -132,6 +163,14 @@ export const authenticated = async (req: express.Request): Promise<boolean> => {
 
       return await isCookieValid(isCookieValidArgs)
     }
+    case AuthType.HttpBasic: {
+      return await validateBasicAuth(
+        req.headers.authorization,
+        req.args["auth-user"],
+        req.args.password,
+        req.args["hashed-password"],
+      )
+    }
     default: {
       throw new Error(`Unsupported auth type ${req.args.auth}`)
     }
diff --git a/src/node/main.ts b/src/node/main.ts
index b3c4e4c14500..a8c8560e18cc 100644
--- a/src/node/main.ts
+++ b/src/node/main.ts
@@ -133,7 +133,7 @@ export const runCodeServer = async (
 
   logger.info(`Using config file ${args.config}`)
   logger.info(`${protocol.toUpperCase()} server listening on ${serverAddress.toString()}`)
-  if (args.auth === AuthType.Password) {
+  if (args.auth === AuthType.Password || args.auth === AuthType.HttpBasic) {
     logger.info("  - Authentication is enabled")
     if (args.usingEnvPassword) {
       logger.info("    - Using password from $PASSWORD")
@@ -142,6 +142,13 @@ export const runCodeServer = async (
     } else {
       logger.info(`    - Using password from ${args.config}`)
     }
+    if (args.auth === AuthType.HttpBasic) {
+      if (args.usingEnvAuthUser) {
+        logger.info("    - Using user from $AUTH_USER")
+      } else {
+        logger.info(`    - With user ${args["auth-user"]}`)
+      }
+    }
   } else {
     logger.info("  - Authentication is disabled")
   }
diff --git a/src/node/routes/domainProxy.ts b/src/node/routes/domainProxy.ts
index 0a9bb4a324f7..e2af5cc4dfac 100644
--- a/src/node/routes/domainProxy.ts
+++ b/src/node/routes/domainProxy.ts
@@ -1,5 +1,6 @@
 import { Request, Router } from "express"
 import { HttpCode, HttpError } from "../../common/http"
+import { AuthType } from "../cli"
 import { getHost, ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
 import { proxy } from "../proxy"
 import { Router as WsRouter } from "../wsRouter"
@@ -78,6 +79,10 @@ router.all(/.*/, async (req, res, next) => {
       if (/\/login\/?/.test(req.path)) {
         return next()
       }
+      // If auth is HttpBasic, return a 401.
+      if (req.args.auth === AuthType.HttpBasic) {
+        throw new HttpError("Unauthorized", HttpCode.Unauthorized)
+      }
       // Redirect all other pages to the login.
       const to = self(req)
       return redirect(req, res, "login", {
diff --git a/src/node/routes/pathProxy.ts b/src/node/routes/pathProxy.ts
index ccfb0cc824a0..848a514f6243 100644
--- a/src/node/routes/pathProxy.ts
+++ b/src/node/routes/pathProxy.ts
@@ -4,6 +4,7 @@ import * as pluginapi from "../../../typings/pluginapi"
 import { HttpCode, HttpError } from "../../common/http"
 import { ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
 import { proxy as _proxy } from "../proxy"
+import { AuthType } from "../cli"
 
 const getProxyTarget = (
   req: Request,
@@ -28,7 +29,7 @@ export async function proxy(
 
   if (!(await authenticated(req))) {
     // If visiting the root (/:port only) redirect to the login page.
-    if (!req.params.path || req.params.path === "/") {
+    if ((!req.params.path || req.params.path === "/") && req.args.auth !== AuthType.HttpBasic) {
       const to = self(req)
       return redirect(req, res, "login", {
         to: to !== "/" ? to : undefined,
diff --git a/src/node/routes/vscode.ts b/src/node/routes/vscode.ts
index 7e8f0f3ff4e5..7e04d5dad49d 100644
--- a/src/node/routes/vscode.ts
+++ b/src/node/routes/vscode.ts
@@ -6,8 +6,9 @@ import * as http from "http"
 import * as net from "net"
 import * as path from "path"
 import { WebsocketRequest } from "../../../typings/pluginapi"
+import { HttpCode, HttpError } from "../../common/http"
 import { logError } from "../../common/util"
-import { CodeArgs, toCodeArgs } from "../cli"
+import { AuthType, CodeArgs, toCodeArgs } from "../cli"
 import { isDevMode, vsRootPath } from "../constants"
 import { authenticated, ensureAuthenticated, ensureOrigin, redirect, replaceTemplates, self } from "../http"
 import { SocketProxyProvider } from "../socket"
@@ -118,6 +119,11 @@ router.get("/", ensureVSCodeLoaded, async (req, res, next) => {
   const FOLDER_OR_WORKSPACE_WAS_CLOSED = req.query.ew
 
   if (!isAuthenticated) {
+    // If auth is HttpBasic, return a 401.
+    if (req.args.auth === AuthType.HttpBasic) {
+      res.setHeader("WWW-Authenticate", `Basic realm="${req.args["app-name"] || "code-server"}"`)
+      throw new HttpError("Unauthorized", HttpCode.Unauthorized)
+    }
     const to = self(req)
     return redirect(req, res, "login", {
       to: to !== "/" ? to : undefined,