SSH Access
Manage host-wide SSH authentication without changing the SSH
port. Every apply is validated, reloaded, reconnected, and audited.
{snapshotLoadError || !current.available ? (
{current.error || snapshotLoadError ||
"The effective SSH policy is unavailable. No change can be applied."}
) : null}
{[
{
label: "Connected user",
value: snapshot.currentUser ?? server.username,
detail:
server.authType === "SSH_KEY"
? "Doktainer uses an SSH key"
: "Doktainer uses a password",
},
{
label: "Connection port",
value: String(server.sshPort),
detail: "Port changes are excluded",
},
{
label: "Effective login",
value: current.passwordAuthentication ? "Key + password" : "Key only",
detail: "Read with sshd -T",
},
{
label: "Root login",
value:
current.permitRootLogin === "no"
? "Blocked"
: current.permitRootLogin === "prohibit-password"
? "Keys only"
: "Allowed",
detail: "Empty passwords are always blocked",
},
].map((item) => (
{item.label}
{item.value}
{item.detail}
))}
{current.temporaryRollbackScheduled ? (
A host-side timer is scheduled to restore the previous SSH policy.
Applying a permanent policy will cancel that timer.
) : null}
How should users sign in?
Choose a preset first. Advanced settings remain available below.
{[
{
value: "key-only" as const,
title: "Key only",
badge: "Recommended",
description: "Production policy using authorized public keys.",
disabled: server.authType === "PASSWORD",
},
{
value: "key-password" as const,
title: "Key and password",
badge: "Easy setup",
description: "Allow either a public key or account password.",
disabled: false,
},
{
value: "temporary" as const,
title: "Temporary password",
badge: `${temporaryDuration} min`,
description: "Enable password login with host-side rollback.",
disabled: temporaryPresetBlocked,
},
].map((preset) => {
const selected = selectedPreset === preset.value;
return (
selectPreset(preset.value)}
title={
preset.disabled
? "Change Doktainer to an SSH key connection before selecting this policy"
: undefined
}
style={{
minHeight: 96,
height: "auto",
padding: 12,
display: "grid",
justifyItems: "start",
alignContent: "center",
gap: 6,
textAlign: "left",
opacity: preset.disabled ? 0.6 : 1,
borderColor: selected
? "rgba(59,130,246,0.5)"
: "var(--border)",
background: selected
? "rgba(59,130,246,0.1)"
: "var(--bg-input)",
}}
>
{preset.title}
{preset.description}
);
})}
{selectedPreset === "temporary" && !temporaryAlreadyEnabled ? (
Restore the previous policy after
setTemporaryDuration(
Number(event.target.value) as 15 | 30 | 60 | 240,
)
}
>
15 minutes
30 minutes
1 hour
4 hours
) : null}
{selectedPreset === "temporary" ? (
Optional account password
Enabling password authentication does not create an account password.
Set one here only when the selected user does not already have it.
{snapshot.nonRootUsers.length > 0 ? (
<>
{
setPasswordUsername(event.target.value);
setPasswordError(null);
}}
>
{snapshot.nonRootUsers.map((user) => (
{user.username}
{user.passwordStatus === "set" ? " (password configured)" : ""}
))}
{
setTemporaryPassword(event.target.value);
setPasswordError(null);
}}
placeholder="Password (minimum 12 characters)"
style={{ width: "100%", paddingRight: 40 }}
/>
setShowTemporaryPassword((value) => !value)}
aria-label={showTemporaryPassword ? "Hide password" : "Show password"}
style={{
position: "absolute",
top: "50%",
right: 5,
width: 28,
minWidth: 28,
height: 28,
padding: 0,
transform: "translateY(-50%)",
}}
>
{showTemporaryPassword ? : }
{
setConfirmTemporaryPassword(event.target.value);
setPasswordError(null);
}}
placeholder="Confirm password"
/>
setForcePasswordChange(event.target.checked)}
/>
Require password change at next login
{passwordError ? (
{passwordError}
) : null}
Review Password Change
>
) : (
Create a non-root user from the Users tab first.
)}
) : null}
setAdvancedOpen((value) => !value)}
aria-expanded={advancedOpen}
style={{ width: "100%", justifyContent: "space-between", padding: "10px 14px" }}
>
Advanced SSH configuration
{advancedOpen ? : }
{advancedOpen ? (
setPermitRootLogin(
event.target.value as "no" | "prohibit-password",
)
}
style={{ width: 150, minWidth: 150, fontSize: 12 }}
>
No
Keys only
undefined}
/>
Managed drop-in
{configPreview}
) : null}
{(activeMethodLocked || rootConnectionBlocked || noAuthenticationMethod) ? (
{rootConnectionBlocked
? "The saved Doktainer connection still uses root. Move it to a non-root account before disabling root login."
: activeMethodLocked
? "The authentication method used by Doktainer must remain enabled."
: "At least one authentication method must remain enabled."}
) : null}
{[
"Write /etc/ssh/sshd_config.d/00-doktainer.conf atomically",
"Validate combined configuration with sshd -t and sshd -T",
"Reload SSH without restarting the daemon",
"Verify a fresh connection and rollback on failure",
].map((step) => (
{step}
))}
{
if (!current.revision) return;
onRequestApplyConfirm({
expectedRevision: current.revision,
pubkeyAuthentication: publicKeyAuthentication,
passwordAuthentication,
permitRootLogin,
permitEmptyPasswords: false,
temporaryMinutes,
});
}}
style={{ justifyContent: "center" }}
>
{actionRunning ? (
) : (
)}
Review & Apply SSH Policy
Reset to Effective Policy
Public keys remain managed per account from Users → Edit → Login Access.
This tab controls the host-wide authentication policy only.
);
}