Right now clickhouse allows to create TTL columns for Distributed tables during alters. It is working normally until server restart. After that it won't start because engine distributed doesn't support TTL clause. Modify metadata is required.

My cluster has 21.3 but tested it also with 21.11 and the same behavior.
It is blocked during create but it should be also be blocked during alters.
Tested on table from tutorial
ENGINE = Distributed('stage, 'default', 'hits_v1') 
alter table hits_v1_all modify column UTMCampaign String TTL EventDate + INTERVAL 1 MONTH ← it passes normally
const ASTPtr & ,
        const StorageMetadataPtr & ,
        ContextPtr ,
        TableExclusiveLockHolder &)
    {
        throw Exception("Truncate is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED);
    }
    virtual void checkTableCanBeRenamed() const {}
    /** Rename the table.
      * Renaming a name in a file with metadata, the name in the list of tables in the RAM, is done separately.
      * In this function, you need to rename the directory with the data, if any.
      * Called when the table structure is locked for write.
      * Table UUID must remain unchanged, unless table moved between Ordinary and Atomic databases.
      */
    virtual void rename(const String & , const StorageID & new_table_id)
    {
        renameInMemory(new_table_id);
    }
    /**
     * Just updates names of database and table without moving any data on disk
     * Can be called directly only from DatabaseAtomic.
     */
    virtual void renameInMemory(const StorageID & new_table_id);
    /** ALTER tables in the form of column changes that do not affect the change
      * to Storage or its parameters. Executes under alter lock (lockForAlter).
      */
    virtual void alter(const AlterCommands & params, ContextPtr context, AlterLockHolder & alter_lock_holder);
    /** Checks that alter commands can be applied to storage. For example, columns can be modified,
      * or primary key can be changes, etc.
      */