Coder powers secure, scalable development across key industries — automotive, finance, government, and technology — enabling faster builds, tighter compliance, and seamless AI adoption in enterprise-grade cloud environments.
This article will walk you through exposing code-server securely once you've
completed the installation process.
Expose code-server
Never expose code-server directly to the internet without some form of
authentication and encryption, otherwise someone can take over your machine via
the terminal.
By default, code-server uses password authentication. As such, you must copy the
password from code-server's config file to log in. To avoid exposing itself
unnecessarily, code-server listens on localhost; this practice is fine for
testing, but it doesn't work if you want to access code-server from a different
machine.
Rate limits: code-server rate limits password authentication attempts to
two per minute plus an additional twelve per hour.
There are several approaches to operating and exposing code-server securely:
Port forwarding via SSH
Using Let's Encrypt with Caddy
Using Let's Encrypt with NGINX
Using a self-signed certificate
Port forwarding via SSH
We highly recommend using port forwarding via
SSH to access
code-server. If you have an SSH server on your remote machine, this approach
doesn't require any additional setup at all.
The downside to SSH forwarding, however, is that you can't access code-server
when using machines without SSH clients (such as iPads). If this applies to you,
we recommend using another method, such as Let's Encrypt instead.
To work properly, your environment should have WebSockets enabled, which
code-server uses to communicate between the browser and server.
SSH into your instance and edit the code-server config file to disable
password authentication:
# Replaces "auth: password" with "auth: none" in the code-server config.
sed -i.bak 's/auth: password/auth: none/' ~/.config/code-server/config.yaml
Restart code-server:
sudo systemctl restart code-server@$USER
Forward local port 8080 to 127.0.0.1:8080 on the remote instance by running the following command on your local machine:
At this point, you can access code-server by pointing your web browser to http://127.0.0.1:8080.
If you'd like to make the port forwarding via SSH persistent, we recommend
using mutagen
to do so. Once you've installed mutagen, you can port forward as follows:
# This is the same as the above SSH command, but it runs in the background
# continuously. Be sure to add `mutagen daemon start` to your ~/.bashrc to
# start the mutagen daemon when you open a shell.
mutagen forward create --name=code-server tcp:127.0.0.1:8080 < instance-ip > :tcp:127.0.0.1:8080
Optional, but highly recommended: add the following to ~/.ssh/config so
that you can detect bricked SSH connections:
You can forward your
SSH and
GPG agent to the instance to
securely access GitHub and sign commits without having to copy your keys.
Using Let's Encrypt with Caddy
Using Let's Encrypt is an option if you want to
access code-server on an iPad or do not want to use SSH port forwarding.
This option requires that the remote machine be exposed to the internet. Make sure that your instance allows HTTP/HTTPS traffic.
You'll need a domain name (if you don't have one, you can purchase one from
Google Domains or the domain service of your
choice). Once you have a domain name, add an A record to your domain that contains your
instance's IP address.
Replace /etc/caddy/Caddyfile using sudo so that the file looks like this:
mydomain.com {
reverse_proxy 127.0.0.1:8080
}
If you want to serve code-server from a sub-path, you can do so as follows:
mydomain.com/code/* {
uri strip_prefix /code
reverse_proxy 127.0.0.1:8080
}
Remember to replace mydomain.com with your domain name!
Reload Caddy:
sudo systemctl reload caddy
At this point, you should be able to access code-server via
https://mydomain.com.
Using Let's Encrypt with NGINX
This option requires that the remote machine be exposed to the internet. Make
sure that your instance allows HTTP/HTTPS traffic.
You'll need a domain name (if you don't have one, you can purchase one from
Google Domains or the domain service of your
choice). Once you have a domain name, add an A record to your domain that contains your
instance's IP address.
We recommend self-signed certificates as a last resort, since self-signed
certificates do not work with iPads and may cause unexpected issues with
code-server. You should only proceed with this option if:
You do not want to buy a domain or you cannot expose the remote machine to
the internet
You do not want to use port forwarding via SSH
To use a self-signed certificate:
This option requires that the remote machine be exposed to the internet. Make
sure that your instance allows HTTP/HTTPS traffic.
SSH into your instance and edit your code-server config file to use a
randomly generated self-signed certificate:
# Replaces "cert: false" with "cert: true" in the code-server config.
sed -i.bak 's/cert: false/cert: true/' ~/.config/code-server/config.yaml
# Replaces "bind-addr: 127.0.0.1:8080" with "bind-addr: 0.0.0.0:443" in the code-server config.
sed -i.bak 's/bind-addr: 127.0.0.1:8080/bind-addr: 0.0.0.0:443/' ~/.config/code-server/config.yaml
# Allows code-server to listen on port 443.
sudo setcap cap_net_bind_service=+ep /usr/lib/code-server/lib/node
Restart code-server:
sudo systemctl restart code-server@$USER
At this point, you should be able to access code-server via
https://<your-instance-ip>.
If you'd like to avoid the warnings displayed by code-server when using a
self-signed certificate, you can use mkcert to create a
self-signed certificate that's trusted by your operating system, then pass the
certificate to code-server via the cert and cert-key config fields.
TLS 1.3 and Safari
If you will be using Safari and your configuration does not allow anything less
than TLS 1.3 you will need to add support for TLS 1.2 since Safari does not
support TLS 1.3 for web sockets at the time of writing. If this is the case you
should see OSSStatus: 9836 in the browser console.
Cloudflare Rocket Loader
If you serve code-server through Cloudflare, disable Rocket Loader for that
hostname. Rocket Loader rewrites inline <script> tags at the edge and
re-injects them from its own loader, and the re-injected scripts do not carry
the nonce in code-server's Content Security Policy, so the browser blocks them.
The result is a blank page: every request returns HTTP 200 and the server logs
look healthy, and the only console output is Cannot determine URI for module id!.
Scope it with a Configuration Rule rather than turning it off zone-wide:
Expression: (http.host eq "code.example.com")
Setting: Rocket Loader -> Off
Adding 'unsafe-inline' to the policy does not help, because CSP ignores it
whenever a nonce or hash is present.
External authentication
If you want to use external authentication mechanism (e.g., Sign in with
Google), you can do this with a reverse proxy such as:
For HTTPS, you can use a self-signed certificate by:
Passing in --cert
Passing in an existing certificate by providing the path to --cert and the
path to the key with --cert-key
The self signed certificate will be generated to
~/.local/share/code-server/self-signed.crt.
If you pass a certificate to code-server, it will respond to HTTPS requests and
redirect all HTTP requests to HTTPS.
You can use Let's Encrypt to get a TLS certificate
for free.
Note: if you set proxy_set_header Host $host; in your reverse proxy config, it
will change the address displayed in the green section of code-server in the
bottom left to show the correct address.
Accessing web services
If you're working on web services and want to access them locally, code-server
can proxy to any port using either a subdomain or a subpath, allowing you to
securely access these services using code-server's built-in authentication.
Using a subdomain
You will need a DNS entry that points to your server for each port you want to
access. You can either set up a wildcard DNS entry for *.<domain> if your
domain name registrar supports it, or you can create one for every port you want
to access (3000.<domain>, 8080.<domain>, etc).
You should also set up TLS certificates for these subdomains, either using a
wildcard certificate for *.<domain> or individual certificates for each port.
To set your domain, start code-server with the --proxy-domain flag:
code-server --proxy-domain <domain>
For instance, if you have code-server exposed on domain.tld and a Python
server running on port 8080 of the same machine code-server is running on, you
could run code-server with --proxy-domain domain.tld and access the Python
server via 8080.domain.tld.
Note that this uses the host header, so ensure your reverse proxy (if you're
using one) forwards that information.
Using a subpath
Simply browse to /proxy/<port>/. For instance, if you have code-server
exposed on domain.tld and a Python server running on port 8080 of the same
machine code-server is running on, you could access the Python server via
domain.tld/proxy/8000.
Using your own proxy
You can make extensions and the ports panel use your own proxy by setting
VSCODE_PROXY_URI. For example if you set
VSCODE_PROXY_URI=https://{{port}}.kyle.dev when an application is detected
running on port 3000 of the same machine code-server is running on the ports
panel will create a link to https://3000.kyle.dev instead of pointing to the
built-in subpath-based proxy.
Note: relative paths are also supported i.e.
VSCODE_PROXY_URI=./proxy/{{port}}
Stripping /proxy/<port> from the request path
You may notice that the code-server proxy strips /proxy/<port> from the
request path.
This reasoning is why the default behavior is to strip /proxy/<port> from the
base path. If your application uses relative URLs and does not assume the
absolute path at which it is being served, it will just work no matter what port
you decide to serve it off or if you put it in behind code-server or any other
proxy.
However, some prefer the cleaner aesthetic of no trailing slashes. Omitting the
trailing slashes couples you to the base path, since you cannot use relative
redirects correctly anymore. If you're okay with this tradeoff, use /absproxy
instead and the path will be passed as is (e.g., /absproxy/3000/my-app-path).
Proxying to create a React app
You must use /absproxy/<port> with create-react-app (see
#2565 and
#2222 for more information).
You will need to inform create-react-app of the path at which you are serving
via $PUBLIC_URL and webpack via $WDS_SOCKET_PATH:
This is a case where you need to serve an application via absproxy as
explained above while serving code-server itself from a path other than the root
in your domain.
For example: http://my-code-server.com/user/123/workspace/my-app. To achieve
this result:
Start code-server with the switch --abs-proxy-base-path=/user/123/workspace
Follow one of the instructions above for your framework.
Preflight requests
By default, if you have auth enabled, code-server will authenticate all proxied
requests including preflight requests. This can cause issues because preflight
requests do not typically include credentials. To allow all preflight requests
through the proxy without authentication, use --skip-auth-preflight.
Internationalization and customization
code-server allows you to provide a JSON file to configure certain strings. This
can be used for both internationalization and customization.
Create a JSON file with your custom strings:
{
"WELCOME": "Welcome to {{app}}",
"LOGIN_TITLE": "{{app}} Access Portal",
"LOGIN_BELOW": "Please log in to continue",
"PASSWORD_PLACEHOLDER": "Enter Password"
}
Then reference the file:
code-server --i18n /path/to/custom-strings.json
Or this can be done in the config file:
i18n: /path/to/custom-strings.json
You can combine this with the --locale flag to configure language support for
both code-server and VS Code in cases where code-server has no support but VS
Code does. If you are using this for internationalization, please consider
sending us a pull request to contribute it to src/node/i18n/locales.
Available keys and placeholders
Refer to ../src/node/i18n/locales/en.json
for a full list of the available keys for translations. Note that the only
placeholders supported for each key are the ones used in the default string.
The --app-name flag controls the {{app}} placeholder in templates. If you
want to change the name, you can either:
Set --app-name (potentially alongside --i18n)
Use --i18n and hardcode the name in your strings
Legacy flag
The --welcome-text flag is now deprecated. Use the WELCOME key instead.